Multi-page website with React in 2020
Summary
I wrote a post in 2017 about the steps it took to set up a simple multi-page website with React from scratch. A lot has changed and improved since then and it’s now much simpler to get started. Here’s the update for 2020!
Follow this tutorial or clone this repo to get started.
Background
Though a lot has gotten simpler in the 3 years since I first wrote my post (create-react-app
wasn’t a workable solution for me back then, for one), there is still a ton of development happening in the React space and best practices change all the time. It’s still harder than it should be to just get to a basic setup to play around with React itself.
Personally, every time I ran npm init
, I was faced with a thousand tutorials that didn’t quite work—either I was cloning git repos with out-of-date package versions, or they included a tool I don’t need, or they weren’t applying today’s best practices. And none of them show you how to deploy the app to someplace like Heroku and have everything, well, just work.
Since my original post, my repo had become painfully out of date. So in an effort to keep helping those just starting out get to the starting line faster, I decided to update the tutorial for 2020.
The “stack”
This tutorial will help you:
- Get to a starting point for creating a multi-page React app that’s hosted for free on Heroku.
- We’ll use React Router to serve up different things at different URLs.
- We’ll wrap our build output thinly with Express to actual serve our pages.
Notes
There are a lot more tools available these days for what’s called the JAMstack, such as Next.js or Gatsby, that you may want to consider if you want to generate static sites that are highly performant and production-ready and don’t rely on a server.
This tutorial is for more of a barebones setup that includes some of the most popular tooling for a good starting point to diving into React.
I’ve chosen Heroku for this tutorial because their Hobby Dyno lets you serve full-fledged web apps for free with some limitations. Many other services, including Render which I’m partial to, have free tiers for static sites, but this tutorial aims to provide a starting point for more dynamic apps as well.
Please get in touch or create a pull request if anything’s amiss!
Prerequisites
This tutorial assumes familiarity with general web development concepts and JavaScript. More tactically, it assumes that you have installed:
- Node (including npm):
$ node -v
v14.4.0
$ npm -v
v6.14.5
$ git --version
git version 2.24.3
$ npm i -g create-react-app
$ create-react-app --version
3.4.1
File setup
Alright, let’s get started. I’m going to create a two-page website that has a “home” page and a “contact me” page.
$ create-react-app react-about-me
Time to install some dependencies for routing and serving:
$ npm i --save react-router-dom express
Now, let’s create some additional files:
$ cd react-about-me && touch server.js && touch src/components/Home.jsx && touch src/components/Contact.jsx
Now our directory should look like:
react-about-me/
├── public/
| |── [... some autogenerated files from create-react-app]
├── src/
| ├── components/
| | ├── Contact.jsx
| | └── Home.jsx
| ├── App.js
| ├── App.css
| └── index.js
| └── [... some autogenerated files from create-react-app]
├── .gitignore
├── index.html
├── package.json
└── server.js
Open the folder with the editor of your choice — I use VSCode.
Components & Routing
We want to show different things on the index page /
and contact page /contact
. We’ll create basic React components for each.
Let’s edit Home.jsx to:
import React, { Component } from 'react';class Home extends Component {
render() {
return (
<div className="Home">
This is the homepage.
</div>
)
}
}export default Home;
Contact.jsx looks very similar:
import React, { Component } from 'react';class Contact extends Component {
render() {
return (
<div className="Contact">
This is the contact page.
</div>
)
}
}export default Contact;
Let’s edit the App.js file to import the components we created:
import React from 'react';import Contact from './components/Contact';
import Home from './components/Home';import './App.css';function App() {
return (
<div className="App">
</div>
);
}export default App;
Routing
We use a module called react-router-dom
to get React to show different views for different URLs.
We’ll add this to App.js to create navigation links and set up our router:
import React from 'react';
import {
BrowserRouter as Router,
Switch,
Route,
NavLink
} from 'react-router-dom';import Contact from './components/Contact';
import Home from './components/Home';import './App.css';function App() {
return (
<div className="App">
<Router>
<nav>
<ul>
<li>
<NavLink
exact to="/"
activeClassName="selected">
Home
</NavLink>
</li>
<li>
<NavLink
to="/contact"
activeClassName="selected">
Contact
</NavLink>
</li>
</ul>
</nav> <Switch>
<Route path="/contact">
<Contact />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</Router>
</div>
);
}export default App;
As you can see, the / path will use the Home component and the /contact path will use the Contact component.
Style
I prefer to write my stylesheets in SASS. I find it’s easier to read and parse. In order to enable that, all you have to do is:
$ npm i --save node-sass
You can now rename .css files to .scss and import them. In App.js:
import './App.scss';
I’ve added some dummy styles to App.scss below to check that SASS is working as expected. In our components, we added the class names Home and Contact to provide selectors for the different pages, and you can eventually use this to design your actual page-specific styling.
$blue: #224AFB;html, body, ul {
background: blue;
margin: 0;
padding: 0;
}.App {
div {
margin-top: 40px;
} a {
color: white; &.selected {
text-decoration: none;
}
} .Home {
color: red;
} .Contact {
color: white;
}
}
Express
We’re going to use express
as a very thin wrapper to serve our app.
First up, let’s make a small change to package.json to let it know to use our version of Node as the engine and to change the entry point from index.js to server.js:
...
"main": "server.js",
"engines": {
"node": ">=14.4.0"
},
...
We can now edit our server.js file to:
const express = require('express');
const app = express();
const path = require('path');const PORT = process.env.PORT || 5000;app.use(express.static(path.join(__dirname, 'build')));
app.listen(PORT);
Running our site
Let’s update our package.json file to add instructions on how to run our app in our dev environment and in production:
...
"scripts": {
"dev": "react-scripts start",
"build": "react-scripts build",
"start": "node server.js",
...
},
...
react-scripts
provides a lot of neat functionality when you’re developing, including “hot reloading” which updates the browser automatically whenever you make any chances to JavaScript files or stylesheets.
Building will eject a production-ready version of your app, which we’ll serve using Express.
With everything set up, you can now run:
npm run dev
If you visit localhost:3000/ and localhost:3000/contact, you should see these works of art:
If you want to run the site against our production setup, you can run:
npm run build && npm start
If you visit localhost:5000/, you’ll see the same site. The main difference with this one is that it’s being served via Express rather than the preview/development mode built into create-react-app
.
Deploying
Commit
If everything looks good so far, let’s commit to git:
$ git add .
$ git commit -am "initial scaffolding for about-me app"
Setting up Heroku
I’m assuming you’ve created an account and followed their guide to get Heroku’s CLI set up. You can then follow the steps on Heroku’s documentation page to deploy your app:
$ heroku create
$ git push heroku master
$ heroku open
And that’s it! Our simple app is now running on Heroku!
Parting thoughts
This tutorial is a lot shorter and simpler than the one I wrote in 2017. The React ecosystem continues to improve and expand in functionality.
As before, these steps leave you at just the beginning. You can now start making this simple app your own by customizing all the things and starting to experiment with the different parts of this particular React “stack”.
While it’ll be difficult for me to keep this tutorial up-to-date (it took me 3 years to update since the first publish 😅), I urge you to submit a pull request to the repo if you notice anything amiss!
Thanks for reading :)