Writing Server-rendered React Apps with Next.js

Share this article


The dust has settled a bit as far as the JavaScript front-end ecosystem is concerned. React has arguably the biggest mindshare at this point, but has a lot of bells and whistles you need to get comfortable with. Vue offers a considerably simpler alternative. And then there are Angular and Ember — which, while still popular, are not the first recommendations for starting a new project.

So, while React is the most popular option, it still requires lot of tooling to write production-ready apps. Create React App solves many of the pain points of starting, but the jury is still out on how long you can go without ejecting. And when you start looking into the current best practices around front-end, single-page applications (SPAs) — like server-side rendering, code splitting, and CSS-in-JS — it’s a lot to find your way through.

That’s where Next comes in.

Why Next?

Next provides a simple yet customizable solution to building production-ready SPAs. Remember how web apps were built in PHP (before “web apps” was even a term)? You create some files in a directory, write your script and you’re good to deploy. That’s the kind of simplicity Next aims at, for the JavaScript ecosystem.

Next is not a brand new framework per se. It fully embraces React, but provides a framework on top of that for building SPAs while following best practices. You still write React components. Anything you can do with Next, you can do with a combination of React, Webpack, Babel, one of 17 CSS-in-JS alternatives, lazy imports and what not. But while building with Next, you aren’t thinking about which CSS-in-JS alternative to use, or how to set up Hot Module Replacement (HMR), or which of many routing options to choose. You’re just using Next — and it just works.

I’d like to think I know a thing or two about JavaScript, but Next.JS saves me an ENORMOUS amount of time. — Eric Elliott

Getting Started

Next requires minimal setup. This gets you all the dependencies you need for starting:

$ npm install next react react-dom --save

Create a directory for your app, and inside that create a directory called pages. The file system is the API. Every .js file becomes a route that gets automatically processed and rendered.

Create a file ./pages/index.js inside your project with these contents:

export default () => (
  <div>Hello, Next!</div>

Populate package.json inside your project with this:

  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"

Then just run npm run dev in the root directory of your project. Go to http://localhost:3000 and you should be able to see your app, running in all its glory!

Just with this much you get:

  • automatic transpilation and bundling (with Webpack and Babel)
  • Hot Module Replacement
  • server-side rendering of ./pages
  • static file serving: ./static/ is mapped to /static/.

Good luck doing that with Vanilla React with this much setup!


Let’s dig into some of the features of modern SPA apps, why they matter, and how they just work with Next.

Automatic code splitting

Why it Matters?
Code splitting is important for ensuring fast time to first meaningful paint. It’s not uncommon to have JavaScript bundle sizes reaching up to several megabytes these days. Sending all that JavaScript over the wire for every single page is a huge waste of bandwidth.

How to get it with Next
With Next, only the declared imports are served with each page. So, let’s say you have 10 dependencies in your package.json, but ./pages/index.js only uses one of them.


import times from 'lodash.times'
export default () => (
  return <div>times(5, <h2> Hello, there! </h2>)</div>;

Now, when the user opens the login page, it’s not going to load all the JavaScript, but only the modules required for this page.

So a certain page may have fat imports, like this:

import React from 'react'
import d3 from 'd3'
import jQuery from 'jquery'

But this won’t affect the performance of the rest of the pages. Faster load times FTW.

Scoped CSS

Why it Matters?
CSS rules, by default, are global. Say you have a CSS rule like this:

.title {
  font-size: 40px;

Now, you might have two components, Post and Profile, both of which may have a div with class title. The CSS rule you defined is going to apply to both of them. So, you define two rules now, one for selector .post .title, the other for .profile .title. It’s manageable for small apps, but you can only think of so many class names.

Scoped CSS lets you define CSS with components, and those rules apply to only those components, making sure that you’re not afraid of unintended effects every time you touch your CSS.

With Next
Next comes with styled-jsx, which provides support for isolated scoped CSS. So, you just have a <style> component inside your React Component render function:

export default () => (
    Hello world
    <p>These colors are scoped!</p>

    <style jsx>{\
      p {
        color: blue;
      div {
        background: red;

You also get the colocation benefits on having the styling (CSS), behavior (JS), and the template (JSX) all in one place. No more searching for the relevant class name to see what styles are being applied to it.

Dynamic Imports

Why it matters?
Dynamic imports let you dynamically load parts of a JavaScript application at runtime. There are several motivations for this, as listed in the proposal:

This could be because of factors only known at runtime (such as the user’s language), for performance reasons (not loading code until it is likely to be used), or for robustness reasons (surviving failure to load a non-critical module).

With Next
Next supports the dynamic import proposal and lets you split code into manageable chunks. So, you can write code like this that dynamically loads a React component after initial load:

import dynamic from 'next/dynamic'

const DynamicComponentWithCustomLoading = dynamic(
    loading: () => <p>The component is loading...</p>

export default () =>
    <Header />
    <DynamicComponentWithCustomLoading />
    <p>Main content.</p>


Why it matters?
A problem with changing pages via JavaScript is that the routes don’t change with that. During their initial days, SPAs were criticized for breaking the web. These days, most frameworks have some robust routing mechanism. React has the widely used react-router package. With Next, however, you don’t need to install a separate package.

With Next
Client-side routing can be enabled via a next/link component. Consider these two pages:

// pages/index.js
import Link from 'next/link'

export default () =>
    Click{' '}
    <Link href="/contact">
    </Link>{' '}
    to find contact information.
// pages/contact.js
export default () => <p>The Contact Page.</p>

Not only that, you can add prefetch prop to Link component, to prefetch pages even before the links are clicked. This enables super-fast transition between routes.

Server rendering

Most of the JavaScript-based SPAs just don’t work without JavaScript disabled. However, it doesn’t have to be that way. Next renders pages on the server, and they can be loaded just like good old rendered web pages when JavaScript is disabled. Every component inside the pages directory gets server-rendered automatically and their scripts inlined. This has the added performance advantage of very fast first loads, since you can just send a rendered page without making additional HTTP requests for the JavaScript files.

Next Steps

That should be enough to get you interested in Next, and if you’re working on a web app, or even an Electron-based application, Next provides some valuable abstractions and defaults to build upon.

To learn more about Next, Learning Next.js is an excellent place to start, and may be all you’ll need.

Jatin ShridharJatin Shridhar
View Author

Jatin is an existing software dev, and aspiring stand up comic. Previously he has worked for Amazon. For a long time, he thought that .js referred to his initials.

Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week
Loading form