React for
Static Websites
Server-Rendered Apps
the Mobile Web

Next.js allows you to leverage the simplicity of the React's component system

And makes it easy-to-use, amplifying its reach

It all started with a paradox

Paradox A: Hacker News

Why are we all sharing cutting edge JS frameworks on a 1990s website 🤔?

No JS, no SPA, no framework rewrite every month?

A: Because it works!

It loads fast. It streams HTML from the server from an in-memory cache of links

Building HN was probably easy and simple

And therefore, it's survived and thrived

We wanted our solution to be able to go up and down the power spectrum

I should be able to render simple websites or powerful dynamic and realtime apps

With one universal programming model (React) and language (JS)

Paradox B: why is this deck written in Keynote?

A: it's not! It's a Next.js deck with SSR and MDX 😎

Next.js has been in production at for almost two years

I'll share some of the principles, patterns, challenges and future directions

1. Automatic code splitting

Minimal Convention over Configuration

Invariant I

sophisticated applications have many variable entry points

Invariant II

loading more code than necessary slows down download and initialization

This is a widely applicable notion

Code splitting is useful even for CLI applications!

Each sub-command is a different entry-point

Code-splitting fixes the SPA Syndrome:

Downloading the code for the Settings section when all you wanted was the About page

Convention: In Next.js, every entry point (top-level component) lives inside `./pages`

It naturally leads to simple project exploration

Emergent Convention

     pages/    top level components (entry points)

       lib/    data fetching and tranformation helpers

components/    standalone and "pure" components

What are some of the consequences of this convention?

Scalable for teams: your components usage on `./pages/x` doesn't affect mine on `./pages/b`

Scalable for teams: distinction of "top-level" vs "building block" components leads to natural style guides

i.e.: in your browser

Top level components define their data needs via an extra lifecycle hook `getInitialProps`

You can decide how much or how little of the page to render

How do you decide?

A: "what's the critical datum for the entry point?"

next-news: `/news` should display… the latest news

next-news: how about `/item?id=13850964`?

A2: "Can my server answer quickly?"

e.g.: Instagram `/explore` page

In other words: whose spinner do you want the user to see? The User-Agent's or yours?

Pre-rendering the layout is typically a better indication of progress

In addition, sometimes data can be loaded from an offline client cache and be best-effort invalidated

2. Pre-fetching

Other entry points can be prefetched via `<Link prefetch>`

`<Link>` allows for page transitions without losing client state

Example: authentication. Load user once, maintain global state, don't refetch, sync upon change

With prefetching + SSR, we get the UX of a SPA with the initial perf of a 1990s website 🎉

3. Route Masking

By default each entry point maps to a route.
e.g.: `/a` renders `./pages/a.js`

However, the server can match any incoming URL to any entrypoint

Design Constraint: the client shouldn't need to load the entire route map ahead of time → `/photo?id=BRbwNt9hEiV`

`<Link href="/photo?id=BRbwNt9hEiV" as="/p/BRbwNt9hEiV">Hi</Link>`

<Link href="" as="">              Router.push(href, as)
<Link href="" as="" replace>      Router.replace(href, as)
<Link href="" prefetch>           Router.prefetch(href)

4. HMR Subscriptions

We build on top of Webpack, which by default watches the entire workdir and re-builds everything

As projects grow in size and number of components, the situation is unsustainable

The situation is worsened when you're editing critical common components

Next.js now automatically subscribes to hot-reloading only the entry points open during dev

This improved iteration performance by 10x-15x for our codebase

5. Immutable Build Artifacts

Since the system loads components lazily, how do we deal with a changing codebase?

Answer: content-addressable build artifacts

`<Link href="/a">`


`<Link href="/3f786850e387550fdab836ed7e6dc881de23001b/a.js">`

If the script fails to load due to a build mismatch,
a full page transition is performed

6. Data Model Agnosticism

It's possible to maintain a global client store. The server can `await` its population

Redux Example

export default connect(state => state)(({ title, linkTo, lastUpdate, light }) => {
  return (
      <Clock lastUpdate={lastUpdate} light={light} />
        <Link href={linkTo}><a>Navigate</a></Link>

Check out the `examples` folder on GitHub:


7. Style system agnosticism

Easiest and most exportable way: inline styles

Default React example

export default () => (
  <div style={styles.main}>
    Hello World

const styles = {
  main: {
    color: blue

Main problem: it's only a strong subset of CSS

No media queries, animations, font declarations, hover states, pseudo elements…

Those are just symptoms. Biggest problem: inhibits co-evolution with CSS

CSS continues to evolve, we should not disregard it

Meet `<style jsx>`

Ahead-of-time Style Isolation via a Babel Transformation

export default () => (
    <style jsx>{`
      p {
        color: red;

Offered as a default, but not prescriptive.
Attempts to solve the "global CSS injection" problem

Out of the box, `<style jsx>` dedupes (only one underlying element), detaches & server-renders

All styling systems supported. Examples folder:


8. Babel extensions

Next.js itself is a babel preset: `next/babel`

You can arbitrary and seamlessly extend Babel settings

In `.babelrc`

  "presets": ["next/babel"],
  "plugins": [

In `pages/index.js`

const greeting = preval`
  const fs = require('fs')
  module.exports = fs.readFileSync(require.resolve('./greeting.txt'), 'utf8')

export default () => <div>{greeting}</div>



9. Server Extensions

const express = require('express')
const next = require('next')

const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()

.then(() => {
  const server = express()

  server.get('/a', (req, res) => {
    return app.render(req, res, '/b', req.query)

  server.get('/b', (req, res) => {
    return app.render(req, res, '/a', req.query)

  server.get('*', (req, res) => {
    return handle(req, res)

  server.listen(3000, (err) => {
    if (err) throw err
    console.log('> Ready on http://localhost:3000')

This API allows you to cache easily at the top level



10. Zones

A frequently overlooked aspect of web services and web sites is how naturally composable and scalable they are.

Split Next.js applications into smaller pieces

Scalable for teams: Smaller surface area

Scalable for teams: Shipped independently

Ongoing Work and Future Directions

1. Serverless Next.js

Faster bootup times


2. Faster compilation

Compiling less

Performance optimations to Webpack

3. Improved error reporting

Find bugs faster

Immediately see the affected source lines when an error is thrown

React for
Static Websites
Server-Rendered Apps
the Mobile Web