close icon
Next

Next.js 3.0 Release: What's New?

Next.js 3.0 has officially been released. What's new? What improvements were made? Learn how to build highly performant Universal JavaScript apps with this new release.

September 27, 2017


TL;DR: On Tuesday, October 25 2016, a small JavaScript framework, Next.js was released to the public. It's a minimal framework for building server-rendered universal JavaScript web apps. Within a few months of its existence, it gathered a lot of attention from the JavaScript community. The React community was set ablaze with joy for finally having a tool that can help build server-side rendering apps without hassle and in-depth technical know-how. In fact, we covered how to build a universal JavaScript web app with it. In this article, I'll highlight the notable additions to Next.js's new release, Next.js 3.0.


Primer: What is a Universal JavaScript Application?

First, I'll provide a little context for the individuals that find Universal JavaScript to be a new term.

The term Universal simply means the ability to run the same code on the server, browsers, mobile devices and any other platform. Universal Javascript is a term people are leaning towards these days. A lot of developers also call it Isomorphic JavaScript. In short, there is a debate on the React repo about this term. Michael Jackson, a popular ReactJS developer, wrote a blog post on Universal JavaScript. It's indeed true that naming things is one of the most difficult aspects of Computer Science.

What's new in Next.js 3.0 ?

1. Dynamic Import Support

Next.js now ships with Dynamic Import. The import function in all its glory allows a codebase to be split into a set of chunks that can be dynamically loaded later.

In Next.js, you can now use dynamic import as seen below:

const lodash = import('lodash');
...
button.addEventListener('click', event => {
    import('./dialogBox.js')
    .then(dialogBox => {
        dialogBox.open();
    })
    .catch(error => {
        /* Error handling */
    })
});

This helps to load functionality on demand. Next.js supports server side rendering for dynamic imports which makes it incredibly awesome for you to avoid displaying the clients blank pages, flickering, or loading spinners.

2. Static Export Support

Next.js now allows you to generate a truly static site by exporting your project to an out directory with .html and .css files. The good thing about this feature is that it was community-driven.

Static Export Support Community Driven Static Export feature

You need to do the following:

  • Create a custom next.config.js file like so:

      exports.exportPathMap = () => ({
        "/": { page: "/" },
        "/about": { page: "/about" }
      });
  • Now run the command like so:

      next build && next export

Note: It's advisable you configure the command in your package.json file like so:

"scripts": {
    "export": "next build && next export",
}

So you can just run npm run export and it will build your Next.js app as a static website. This simply means you don't need any server to deploy it. Whoop! Whoop!

Change directory into the new out directory and deploy your app to a cloud platform, e.g [now](https://vercel.com/home).

3. Better Error Handling

The error color theme has been updated to be more accessible and easier on the eyes.

Beautiful Error Handling

Better Error Handling

Live error reloading Source: Zeit

4. Improved Startup Time

Startup time for a Next.js app is now 5 times faster. The bootup time for a typical Next.js 3 app was cut down from 1000ms to about 200ms.

5. Optimized Bundle

The bundle size of Next.js core is now smaller. In fact, here is the webpack bundle analyzer output after optimization:

Optimized Bundle Optimized Bundle

6. Improved Hot Module Replacement

Before now, there were some bugs with HMR, (hot module replacement), One of which was the ERR_INCOMPLETE_CHUNK_ENCODING error that shows up when using Node.js 8.0. That issue has been solved. Yaay!

Furthermore, if you return a wrong type, Next.js shows you the right error message and also recovers smoothly from it once the right type is returned.

Returning of Bad Type Better Bad Type Returns

One more thing: undefined is not a function is now obsolete. Next.js now correctly identifies any type of runtime error thrown and catches it effectively. A typical example of this scenario can be seen below:

Undefined is now a function Source: Next.js Blog

7. Dynamic React Components

Next.js now ships with a powerful opt-in utility called next/dynamic which helps you to create dynamically loaded React Components easily.

Before now, code splitting was route based. In Next.js 3, you will be able to load code as a function of the data that the user gets.

import dynamic from 'next/dynamic'
const DialogComponent = dynamic(import('../components/DialogBox'))

export default () => (
  <div>
    <Header />
    <DialogComponent />
    <p>Weclome to the landing Page</p>
  </div>
)

Note: If the dynamic component is loaded in the initial rendering, server-rendering also works. Awesome!

Aside: Authenticating a Next.js 3.0 App with Auth0

Auth0 issues JSON Web Tokens on every login for your users. This means that you can have a solid identity infrastructure, including single sign-on, user management, support for social identity providers (Facebook, Github, Twitter, etc.), enterprise identity providers (Active Directory, LDAP, SAML, etc.) and your own database of users with just a few lines of code.

We can easily set up authentication in a Next.js 3.0 apps by using the Auth0.js library. If you don't already have an Auth0 account, sign up for one now. Navigate to the Auth0 management dashboard, click on New client by the right hand side, select Regular Web App from the dialog box and then go ahead to the Settings tab where the client ID, client Secret and Domain can be retreived.

Auth0 offers a generous free tier to get started with modern authentication.

Note: Make sure you set the Allowed Callback URLs to http://localhost:3000/auth/signed-in or whatever url/port you are running on. Furthermore; set the Allowed Logout URLs to http://localhost:3000/.

Authentication in a Next.js app could be a little complicated because you have to ensure that the server-rendered pages are authenticated, meaning they need to have access to the token.

Check out the complete app on Github.

Note: Don't forget to rename the config.sample.json file to config.json and add your credentials.

utils/auth.js

import jwtDecode from 'jwt-decode'
import Cookie from 'js-cookie'

const getQueryParams = () => {
  const params = {}
  window.location.href.replace(/([^(?|#)=&]+)(=([^&]*))?/g, ($0, $1, $2, $3) => {
    params[$1] = $3
  })
  return params
}

export const setToken = (idToken, accessToken) => {
  if (!process.browser) {
    return
  }
  Cookie.set('user', jwtDecode(idToken))
  Cookie.set('idToken', idToken)
  Cookie.set('accessToken', accessToken)
}

export const unsetToken = () => {
  if (!process.browser) {
    return
  }
  Cookie.remove('idToken')
  Cookie.remove('accessToken')
  Cookie.remove('user')

  // to support logging out from all windows
  window.localStorage.setItem('logout', Date.now())
}

export const getUserFromServerCookie = (req) => {
  if (!req.headers.cookie) {
    return undefined
  }
  const jwtCookie = req.headers.cookie.split(';').find(c => c.trim().startsWith('idToken='))
  if (!jwtCookie) {
    return undefined
  }
  const jwt = jwtCookie.split('=')[1]
  return jwtDecode(jwt)
}

export const getUserFromLocalCookie = () => {
  return Cookie.getJSON('user')
}

utils/auth0.js

const getAuth0 = (options) => {
  const config = require('../config.json')
  const auth0 = require('auth0-js');
  return new auth0.WebAuth({
    clientID: config.AUTH0_CLIENT_ID,
    domain: config.AUTH0_CLIENT_DOMAIN,
  });
}

const getBaseUrl = () => `${window.location.protocol}//${window.location.host}`

const getOptions = (container) => {
  return {
    responseType: 'token id_token',
    redirectUri: `${getBaseUrl()}/auth/signed-in`,
    scope: 'openid profile email'
  }
}

export const authorize = () => getAuth0().authorize(getOptions())
export const logout = () => getAuth0().logout({ returnTo: getBaseUrl() })
export const parseHash = (callback) => getAuth0().parseHash(callback)

pages/auth/sign-in.js

import React from 'react'

import defaultPage from '../../hocs/defaultPage'
import { authorize } from '../../utils/auth0'

class SignIn extends React.Component {
  componentDidMount () {
    authorize()
  }
  render () {
    return null
  }
}

export default defaultPage(SignIn)

Display the login page once the sign-in component gets mounted.

Sign in Sign-in page

pages/auth/signed-in.js

import React, { PropTypes } from 'react'
import Router from 'next/router'

import { setToken } from '../../utils/auth'
import { parseHash } from '../../utils/auth0'

export default class SignedIn extends React.Component {
  static propTypes = {
    url: PropTypes.object.isRequired
  }

  componentDidMount () {
    parseHash((err, result) => {
      if(err) {
        console.error('Something happened with the Sign In request')
        return;
      }

      setToken(result.idToken, result.accessToken);
      Router.push('/')
    })
  }
  render () {
    return null
  }
}

Grab the token and ID token from Auth0 as it returns to the callback which is the signed-in page, save it and redirect to the index page.

Signed in Secret page shows that the user is signed in and can access it

pages/index.js

import React, { PropTypes } from 'react'
import Link from 'next/link'

import defaultPage from '../hocs/defaultPage'

const SuperSecretDiv = () => (
  <div>
    This is a super secret div.
    <style jsx>{`
      div {
        background-color: #ecf0f1;
        box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
        border-radius: 2px;
        padding: 10px;
        min-height: 100px;
        display: flex;
        align-items: center;
        justify-content: center;
        color: #333;
        text-align: center;
        font-size: 40px;
        font-weight: 100;
        margin-bottom: 30px;
      }
    `}</style>
  </div>
)

const createLink = (href, text) => (
  <a href={href}>
    {text}
    <style jsx>{`
      a {
        color: #333;
        padding-bottom: 2px;
        border-bottom: 1px solid #ccc;
        text-decoration: none;
        font-weight: 400;
        line-height: 30px;
        transition: border-bottom .2s;
      }

      a:hover {
        border-bottom-color: #333;
      }
    `}</style>
  </a>
)

const Index = ({ isAuthenticated }) => (
  <div>
    {isAuthenticated && <SuperSecretDiv />}
    <div className='main'>
      <h1>Hello, friend!</h1>
      <p>
        This is a super simple example of how to use {createLink('https://github.com/zeit/next.js', 'next.js')} and {createLink('https://auth0.com/', 'Auth0')} together.
      </p>
      {!isAuthenticated && (
        <p>
          You're not authenticated yet. Maybe you want to <Link href='/auth/sign-in'>{createLink('/auth/sign-in', 'sign in')}</Link> and see what happens?
        </p>
      )}
      {isAuthenticated && (
        <p>
          Now that you're authenticated, maybe you should try going to our <Link href='/secret'>{createLink('/secret', 'super secret page')}</Link>!
        </p>
      )}
    </div>
    <style jsx>{`
      .main {
        max-width: 750px;
        margin: 0 auto;
        text-align: center;
      }

      h1 {
        font-size: 40;
        font-weight: 200;
        line-height: 40px;
      }

      p {
        font-size: 20px;
        font-weight: 200;
        line-height: 30px;
      }
    `}</style>
  </div>
)

Index.propTypes = {
  isAuthenticated: PropTypes.bool.isRequired
}

export default defaultPage(Index)

The index page is server-rendered. It checks if the user is authenticated or not and renders content based on the status.

The secret page too checks if the user is logged in and determines content based on the user's status.

Secret page unauthorized Not displaying valid content because the user cant access the secret page without signing in

Note: This example performs no server side validation of the token sent by the user in its cookies. For production-ready secure pages this is necessary.

Auth0 provides the simplest and easiest to use user interface tools to help administrators manage user identities including password resets, creating and provisioning, blocking and deleting users.

Conclusion

With Next.js 3, the GitHub repo now has over 16,000 stars and we have seen lots of significant improvements and major upgrades from the initial version that was released last year. Kudos to the team behind this project and the JavaScript community for their continuous support. In fact, they already have plans for Next.js 4.

  • Twitter icon
  • LinkedIn icon
  • Faceboook icon