Blog post
Getting started with Next.js
Getting started with Next.js
Nikola Ivanović
2021-02-18
Working on a modern JavaScript application powered by React is awesome until you realize a couple of problems related to rendering all the content on the client-side.
Problems:
The page takes longer to become visible to the user because, before the content loads, all the JavaScript must load, and your application needs to run to determine what to show on the page.
If you are building a publicly available website, you have a content SEO issue. Search engines are getting better at running and indexing JavaScript apps, but it's much better to send them pre-compiled content instead of letting them figure it out.
The solution to both of those problems is server rendering, also called static pre-rendering.
Next.js is a React framework to do all of this effortlessly and has many other features.
Hot Code Reloading - Next.js reloads the page when it detects any change saved to disk.
Automatic Routing - Any URL is mapped to the file-system, to files put in the pages
folder, and you don't need any configuration.
Single File Components - Using styled-jsx
, completely integrated as built by the same team, it's trivial to add styles scoped to the component.
Server Rendering - One of the main features which give you the possibility to render React components on the server-side before sending the HTML to the client.
Ecosystem Compatibility - Great compatibility with the Node and React ecosystem.
Automatic Code Splitting - Instead of generating one single JavaScript file containing all the app code, the app is broken up automatically by Next.js in several different resources, which means loading a page only loads the JavaScript necessary that particular page. This ensures your first-page load is as fast as it can be!
Prefetching - The Link
component, which links together different pages, supports a prefetch prop, which automatically prefetches
page resources in the background.
First of all, you need to have Node.js installed. You can get it on https://nodejs.org/.
There are 2 ways to install Next.js. The first way is by using the create-next-app
command and the second one is the classic approach, which involves installing and setting up the Next.js app manually.
In this text, the first way will be explained.
In your command line, use the following line of code:
1npx create-next-app
The command asks the application name (and creates a new folder for you with that name), then downloads all the packages it needs (react
, react-dom
, next
), sets the package.json
to:
and then, you can run the sample app by running npm run dev
, which will show the result on http://localhost:3000 like this:
This is the recommended way to start a Next.js application, as it gives you structure and sample code to play with.
You can also use any examples stored on the Examples page using the --example
option. For example, try:
1npx create-next-app --example blog-starter
To check if one of Next.js' main selling points of Next.js, which is server-side rendering, is working, while using Chrome, right-click anywhere on the page and select View Page Source.
Copy the code that you see on the screen and paste it into HTML Formatter for better observation. You will then see something like this:
We have 4 JavaScript files being declared to be preloaded in the head
, using rel="preload" as="script"
.
This tells the browser to start loading those files as soon as possible before the normal rendering flow starts. Without those, scripts would be loaded with an additional delay, improving the page loading performance.
If you do, you can hover it and see that it says: “Prerendered Page“.
This icon is only visible in development mode and tells you that the page does not depend on data that needs to be fetched at invocation time, and it can be prerendered and built as a static HTML file when we run npm run build
.
Next.js can determine this by the absence of the getInitialProps()
method attached to the page component.
When this is the case, our page can be even faster because it will be served statically as an HTML file rather than going through the Node.js server that generates the HTML output.
Another useful icon that might appear next to it, or instead of it on non-prerendered pages, is a little animated triangle:
This is a compilation indicator and appears when you save a page, and Next.js is compiling the application before hot code reloading steps in to automatically reload the code in the application.
I want to add a Blog page to this website. It's going to be served into /blog
, and for the time being, it will just contain a simple static page, just like our first index.js
component:
After saving the new file, the npm run dev
process already running can render the page without restarting it.
When we go to URL http://localhost:3000/blog, our new page is shown:
And it is also good to mention what the terminal told us:
As we can see, the URL /blog depends on the file name and its position under the pages folder, and it doesn’t matter what the name of the component (our component’s name starts with capital “B“) is.
Now, since we have 2 pages, we can introduce links.
We won’t be using classic a
tag because we are using Next.js, and we want fast transitions to other pages.
If we use a
tag to navigate our Blog page, we will get all the JavaScript from the server, but we don’t actually need it. We just need a blog.js
page bundle, the only one that is new to the page.
To fix this problem, we use a component provided by Next, called Link.
First of all, we need to import it:
1import Link from 'next/link'
and then, we use it to wrap our link:
If you compare loading times after clicking on the link with classic a
tag and Link from Next.js in the developer tools in the Network tab, you will see how much faster the second method is. This is client-side rendering in action. Please don’t believe me, try it.
Also, one more thing. What if you now press the back button? Nothing is being loaded because the browser still has the old index.js
bundle in place, ready to load the /index
route. It's all automatic.
In the previous chapter, we saw how to link the home to the blog page.
A blog is a great use case for Next.js, one we'll continue to explore in this chapter by adding blog posts.
This content is dynamic and might be taken from a database, markdown files, or more.
Next.js can serve dynamic content based on a dynamic URL.
We create a dynamic URL by creating a dynamic page with the []
syntax.
How? We add a pages/blog/[id].js
file. This file will handle all the dynamic URLs under the /blog/
route.
In the file name, [id]
inside the square brackets means that anything dynamic will be put inside the router's query property's id parameter.
But first of all, what is a router?
The router is a library provided by Next.js.
We import it from next/router
:
1import { useRouter } from 'next/router'
and once we have useRouter
, we instantiate the router object using:
1const router = useRouter()
Once we have this router object, we can extract information from it.
In particular, we can get the URL's dynamic part in the [id].js
file by accessing the router.query.id
.
The dynamic part can also just be a portion of the URL, like post-[id].js
.
So let's go on and apply all those things in practice.
Create the file pages/blog/[id].js
:
Now, if you go to the http://localhost:3000/blog/new-post
router, you should see this:
We can use this id
parameter to gather the post from a list of posts. From a database, for example. To keep things simple, we'll add a posts.json
file in the project root folder:
1{
2 "first": {
3 "title": "first post",
4 "content": "Hey this is our first post!"
5 },
6 "second": {
7 "title": "second post",
8 "content": "Hey this is the second post content"
9 }
10}
Now we can import it and lookup the post from the id
key:
And reloading the page should give us the post with title and content. But it doesn’t… It gives us an error: Cannot read property 'title' of undefined.
During rendering, when the component is initialized, the data is not there yet. We'll see how to provide the data to the component with getInitialProps in the next chapter.
For now, we will use good old if (!post) return <p></p>
before returning JSX:
Change the URL's id to 'first', and now we have our first post visible on the page! Congratulations!
After rendering, Next.js triggers an update with the query value, and the page displays the correct information, but if you view the source code, there is that empty <p>
tag in the HTML.
We'll soon fix this issue that fails to implement SSR, and this harms both loading times for our users, SEO, and social sharing, as we already discussed.
We can complete the blog example by listing those posts in pages/blog.js
:
And we can link them to the individual post pages by importing Link
from next/link
and using it inside the posts loop:
As mentioned, the Link
Next.js component can be used to create links between 2 pages. When you use it, Next.js transparently handles frontend routing for us, so when a user clicks a link, the frontend takes care of showing the new page without triggering a new client/server request and response cycle, as it normally happens with web pages.
There's another thing that Next.js does for you when you use Link
.
As soon as an element wrapped within <Link>
appears in the viewport (which means it's visible to the website user), Next.js prefetches the URL it points to, as long as it's a local link (on your website), making the application super fast to the viewer.
This behavior is only being triggered in production mode (we'll talk about this in-depth later), which means you have to stop the application if you are running it with npm run dev
, compile your production bundle with npm run build
and run it with npm run start
instead.
Using the Network inspector in the DevTools, you'll notice that any links above the fold, at page load
, start the prefetching as soon as the load event has been fired on your page (triggered when the page is fully loaded and happens after the DOMContentLoaded
event).
Any other Link
tag not in the viewport will be prefetched when the user scrolls and gets into the viewport.
Prefetching is automatic on high-speed connections (Wifi and 3g+ connections unless the browser sends the Save-Data
HTTP Header.
You can opt-out from prefetching individual Link
instances by setting the prefetch
prop to false
:
1<Link href="/a-link" prefetch={false}>
2 <a>A link</a>
3</Link>
One handy feature when working with links is determining what the current URL is, particularly assigning a class to the active link to style it differently from the other ones.
This is especially useful in your site header, for example.
The Next.js default Link
component offered in next/link
does not do this automatically for us.
We can create a Link component ourselves, and we store it in a file Link.js
in the Components folder, and import that instead of the default next/link
in our index.js
.
In this component, we'll first import React from react
, Link from next/link
, and the useRouter
hook from next/router
.
Inside the component, we determine if the current pathname matches the component's href
prop and if so, we append the selected
class to the children.
We finally return children with the updated class, using React.cloneElement()
.
Here is the code for the new Link
component :
1import React from 'react'
2import Link from 'next/link'
3import { useRouter } from 'next/router'
4
5export default ({ href, children }) => {
6 const router = useRouter()
7
8 let className = children.props.className || ''
9 if (router.pathname === href) {
10 className = `${className} selected`
11 }
12
13 return <Link href={href}>{React.cloneElement(children, { className })}</Link>
14}
We already saw how to use the Link component to handle routing in Next.js apps declaratively.
It's convenient to manage routing in JSX, but sometimes you need to trigger a routing change programmatically.
In this case, you can access the Next.js Router directly, provided in the next/router
package, and call its push()
method.
Here's an example of accessing the router:
1import { useRouter } from 'next/router'
2
3export default () => {
4 const router = useRouter()
5 //...
6}
Once we get the router object by invoking useRouter()
, we can use its methods.
This is the client-side router, so methods should only be used in frontend facing code. The easiest way to ensure this is to wrap calls in the useEffect()
React hook or inside componentDidMount()
in React stateful components.
The ones you'll likely use the most are push()
and prefetch()
.
push()
allows us to trigger a URL change in the frontend programmatically:
1router.push('/login')
prefetch()
allows us to programmatically prefetch a URL, useful when we don't have a Link tag that automatically handles prefetching for us:
1router.prefetch('/login')
Full example:
1import { useRouter } from 'next/router'
2
3export default () => {
4 const router = useRouter()
5
6 useEffect(() => {
7 router.prefetch('/login')
8 })
9}
You can also use the router to listen for route change events.
Remember when we got the Cannot read property ‘title' of undefined error, and we had to use if (!post)
to solve it? If you don’t, I’ve just reminded you that there is a more elegant way to solve it.
We will also make SSR work with dynamic routes.
We must provide the component with props, using a special function called getInitialProps()
attached to the component.
To do so, first, we name the component:
1const Post = () => {
2 //...
3}
4
5export default Post
then we add the function to it:
1const Post = () => {
2 //...
3}
4
5Post.getInitialProps = () => {
6 //...
7}
8
9export default Post
This function gets an object as its argument, which contains several properties. In particular, we are interested in now that we get the query
object, the one we used previously to get the post id.
So we can get it using the object destructuring syntax:
1Post.getInitialProps = ({ query }) => {
2 //...
3}
Now we can return the post from this function:
1Post.getInitialProps = ({ query }) => {
2 return {
3 post: posts[query.id]
4 }
5}
And we can also remove the import of useRouter
, and we get the post from the props
property passed to the Post
component, so the final code should look like this:
Now there will be no error, and SSR will be working as expected, which you can check by viewing the source.
The getInitialProps
function will be executed on the server-side and the client-side when we navigate a new page using the Link
component.
It's important to note that getInitialProps
gets, in the context object it receives, in addition to the query
object, these other properties:
pathname
: the path
section of URL
asPath
- String of the actual path (including the query) shows in the browser
which in the case of calling http://localhost:3000/blog/first
will respectively result to:
/blog/[id]
/blog/test
For styling components in Next.js, we can use its library, which is styled-jsx
built-in.
It affects only the component where the styled-jsx
is applied.
The code is written like this:
and in my opinion, it is good only for some smaller components where you need to add just a little bit of styling, but if the component has more elements, then it might be a little bit too much of a code mixed up in one component. In that case, my suggestion is to use a classic import method of a CSS file, which includes a simple configuration.
First, we need to install @zeit/next-css
:
1npm install @zeit/next-css
and then create a configuration file in the root of the project, called next.config.js
, with this content:
1const withCSS = require('@zeit/next-css')
2module.exports = withCSS()
After restarting the Next app, you can now import CSS like you normally do with JavaScript libraries or components:
1import '../style.css'
You can also import a SASS file directly, using the @zeit/next-sass
library instead.
A styled-jsx library - explained in details.
From any Next.js page component, you can add information to the page header.
This is handy when:
you want to customize the page title
you want to change a meta tag
How can you do so?
Inside every component, you can import the Head
component from next/head
and include it in your component JSX output:
You can add any HTML tag you'd like to appear in the <head>
section of the page.
When mounting the component, Next.js will make sure the tags inside Head
are added to the page's heading. Same when unmounting the component, Next.js will take care of removing those tags.
In addition to creating page routes, which means pages are served to the browser as Web pages, Next.js can create API routes. It means that Next.js can be used to create a frontend for data stored and retrieved by Next.js itself, transferring JSON via fetch requests.
API routes live under the /pages/api/
folder and are mapped to the /api
endpoint.
In those routes, we write Node.js code (rather than React code). It's a paradigm shift; you move from the frontend to the backend, but very seamlessly.
For example, you have a /pages/api/comments.js
file, whose goal is to return the comments of a blog post as JSON.
And you have a list of comments stored in a comments.json
file:
1[
2 {
3 "comment": "This is great!"
4 },
5 {
6 "comment": "Nice post!"
7 }
8]
Here's a sample code, which returns the client the list of comments:
1import comments from './comments.json'
2
3export default (req, res) => {
4 res.status(200).json(comments)
5}
It will listen on the /api/comments
URL for GET requests, and you can try calling it using your browser:
API routes can also use dynamic routing like pages, use the []
syntax to create a dynamic API route, like /pages/api/comments/[id].js
, which will retrieve the comments specific to a post id.
Inside the [id].js
, you can retrieve the id
value by looking it up inside the req.query
object:
1import comments from '../comments.json'
2
3export default (req, res) => {
4 res.status(200).json({ post: req.query.id, comments })
5}
And then you will get something like this:
You can execute code only on the server-side or on the client-side by checking the window
property in your page components.
This property is only existing inside the browser, so that you can check
1if (typeof window === 'undefined') {
2
3}
and add the server-side code in that block.
Similarly, you can execute client-side code only by checking.
1if (typeof window !== 'undefined') {
2
3}
Remember in the "How to install Next.js" chapter, and we added those 3 lines to the package.json script
section:
1"scripts": {
2 "dev": "next",
3 "build": "next build",
4 "start": "next start"
5}
We used npm run dev
up to now to call the next
command installed locally in node_modules/next/dist/bin/next
. This started the development server, which provided us source maps and hot code reloading, two handy features while debugging.
The same command can be invoked to build the website passing the build
flag by running npm run build
. The same command can then be used to start the production app, passing the start
flag, by running npm run start
.
Those 2 commands are the ones we must invoke to deploy the production version of our site locally.
So, let's create a production deployment of our app. Build it using:
1npm run build
The command's output tells us that some routes (/
and /blog
are now prerendered as static HTML, while /blog/[id]
and /api/comments
will be served by the Node.js backend.
Then you can run npm run star
t to start the production server locally:
1npm run start
Visiting http://localhost:3000 will show us the production version of the app locally.
This was the basics tutorial about Next.js where I got to introduce you to the possibilities that Next.js can provide you with. There is a lot more to know about Next.js: managing user sessions with login, serverless, managing databases, and so on…
The next step I recommend is to take a good read at the Next.js official documentation to find out more about all the features and functionality I didn't talk about and take a look at all the additional functionalities introduced by Next.js plugins, some of which are pretty amazing.
Nikola Ivanović
2021-02-18
Nikola is JavaScript developer passionate about React, CSS, Animations and UX design. Loves frontend work but he is not afraid to touch backend.
Leave your thought here
Your email address will not be published. Required fields are marked *