How To Build Your Website With NextJS
Tutorial on the basics of NextJS for building a website.
Summary
Most of the front-end applications using React that I've been able to work on are browser-based (client-side) applications.
However, depending on the tools you use, they may not be visible to search engines and therefore prevent a good indexing of your content (SEO).
To overcome this problem, frameworks have been developed to make server-side React applications possible. This is the case of Next.JS which we will study in this article.
I had a React application used client-side and migrated it to a server side rendering in just a few hours thanks to the framework. The purpose of this article is to share with you my technical experience on this migration.
First of all, we need to look at what Next.JS has to offer so that we can see if this framework fits our needs:
static
directory,pages
directory,So you should have everything you need to migrate your project. Let's get started!
The first thing to do is to add our dependencies to the package. json
file:
{ "version": "0.0.1", "private": true, "dependencies": { + "next": "^3.0.3", + "next-redux-wrapper": "^1.3.2", + "next-routes": "^1.0.40", "prop-types": "^15.5.10", "react": "^15.5.4", "react-dom": "^15.5.4", "react-scripts": "1.0.1" }, "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test --env=jsdom", + "dev": "next dev src", + "start": "node server.js", + "build": "next build src", + "test": "react-scripts test --env=jsdom" } }
We therefore add here our three dependencies: next
, next-redux-wrapper
and next-routes
. No need to go into more details on the subject, to me the name of the libraries seems clear enough to guess their usefulness.
We also need to modify the scripts. Indeed, we will now need a node
HTTP server and will use the `next' library to build and develop our application with an on-the-fly compiler.
Note that for the moment, I have kept react-scripts
to run my tests but this one could of course be deleted.
We will respect the conventions proposed by Next.JS, so we will place our different pages in a directory named page
, and our static files in a static
directory.
The only difference is that we will place them in src
. This is why we have specified this directory in the previous commands.
So here's our project structure:
.
├── src
│ ├── actions
│ │ ├── ...
│ ├── components
│ │ ├── ...
│ ├── constants
│ │ └── ...
│ ├── containers
│ │ ├── ...
│ ├── pages
│ │ ├── _error.js
│ │ ├── index.js
│ │ └── category.js
│ ├── reducers
│ │ ├── ...
│ ├── static
│ │ ├── css
│ │ │ ├── ...
│ │ ├── fonts
│ │ │ └── ...
│ │ ├── img
│ │ │ ├── ...
│ │ ├── favicon.ico
│ │ └── index.html
│ ├── store
│ │ └── configureStore.js
│ ├── translations
│ │ ├── ...
│ ├── index.js
│ └── routes.js
├── tests
│ └── ...
├── Makefile
├── README.md
├── package-lock.json
├── package.json
├── server.js
We kept the same file structure we had with our client-side project and simply added the pages
and static
directories (which were previously named assets
in our case).
You can already run the server on your development machine by running:
$ npm run dev (ou yarn dev)
On the production side, the server.js
node server will be executed. Here's his code:
const next = require('next')
const routes = require('./src/routes')
const app = next({dir: 'src', dev: false})
const handler = routes.getRequestHandler(app)
const {createServer} = require('http')
app.prepare().then(() => {
createServer(handler).listen(8081)
})
We instantiate here Next.JS and retrieve the routes we will create later.
We also specify the src
directory and specify that you are not in a development environment to optimize the build.
Now it's time to write our first server-side page!
We will start with a very simple homepage. Place a file at src/pages/index.js
:
import React from 'react'
import withRedux from 'next-redux-wrapper'
import { store } from '../store/configureStore'
import { Footer, Homepage } from '../components';
import { AppContainer, HeaderContainer } from '../containers';
const Page = () => (
<AppContainer className='App Homepage'>
<div>
<HeaderContainer />
<Homepage />
<Footer />
</div>
</AppContainer>
)
export default withRedux(store)(Page)
Very simple, we instantiate a Page
React object comprising the components that will form our page and call a function returned by withRedux(store)
(provided by the next-redux-wrapper
library) to synchronize our Redux store on this page.
This way, our entire page (and the components included inside) will have access and can manipulate the Redux store.
If you go to http://localhost:3000/
now, your homepage should be visible!
Unfortunately, it is a bit complicated to declare custom routes and/or containing variables directly via Next.JS framework, that's why I choose to use the next-routes
library which will allow me to do this very simply.
For this example, we will start with a /category/: slug
URL.
We must then declare a file to src/routes.js
with the following content:
const routes = module.exports = require('next-routes')()
routes
.add('category', '/category/:slug')
When the URL /category/: slug
is called (or slug is a dynamic value), the src/pages/category.js
file will be called.
Let's create this file:
import React from 'react'
import withRedux from 'next-redux-wrapper'
import { store } from '../store/configureStore'
import { Category, Footer } from '../components';
import { AppContainer, HeaderContainer, } from '../containers';
const Page = ({ slug }) => (
<AppContainer className='App Homepage'>
<div>
<HeaderContainer displaySearch={true} />
<Category slug={slug} />
<Footer />
</div>
</AppContainer>
)
Page.getInitialProps = async ({ query }) => {
return { slug: query.slug }
}
export default withRedux(store)(Page)
As you can see here, we have added the following code to the component, allowing us to retrieve the slug
parameter from the request and send it to our page component in its props (and then to our Category
component):
Page.getInitialProps = async ({ query }) => { return { slug: query.slug } }
Really simple, isn't it? You now know how to perform dynamic routing!
The getInitialProps()
method presented above will also allow you to make server-side requests, which means that the end client will not know about it.
This way, you will be able to retrieve the API data and insert it into your store.
In the templates of your React components, you can declare links to your routes this way, by importing the Link
component available through your routes.js
file and then:
import { Link } from '../routes'; <Link route='category' params={{slug: 'hello-world'}}> Go to hello-world category </Link>
Note that you can also use the following notation:
<Link route='category/hello-world'>
Note : You can add a
prefetch
attribute if you want to preload your pages.
Next.JS also provides a Head
component that will allow you, as its name suggests, to manipulate the <head>
HTML of your page.
To define the <title>
of your page, you must write:
import Head from 'next/head';
const Page = () => (
<Head>
<title>My homepage title</title>
</Head>
...
)
Another element that will allow you to improve your referencing and to further personalize your pages.
Migrating from a client-side React application (initialized with create-react-app
) took me only a few hours because the Next.JS
framework is really easy to use and the community already quite large.
In fact, Next.JS integrates very well with most of the React ecosystem tools.
I hope this article will help you migrate your client-side application to server-side.
Author(s)
Vincent Composieux
Architecte passionné par les technologies web depuis de longues années, je pratique principalement du PHP (Symfony) / Javascript mais aussi du Python ou Golang.
You wanna know more about something in particular?
Let's plan a meeting!
Our experts answer all your questions.
Contact usDiscover other content about the same topic
Tutorial on the basics of NextJS for building a website.
You may not be using the React states optimally and I'll explain why
In this article we are going to cover how to set up i18next in your React app