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://zeit.co/now).

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 Lock Widget. 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. And set the Allowed Origins (CORS) to http://localhost:3000/ or whatever domain url you are using, especially if it is hosted. Furthermore; set the Allowed Logout URLs to http://localhost:3000/.

Here, we use Lock, so it's important to go to Dashboard >> Clients >> Settings >> Show Advanced Settings >> OAuth >> OIDC Conformant flag and switch off the OIDC Conformat flag because it uses the legacy authentication pipeline. It's easier to upgrade to next 3.0 without breaking changes. Check out the OIDC Conformant Authentication Adoption Guide.

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.

In the example below, the token returned from Auth0 is stored in LocalStorage and also as a cookie.

Check out the completed 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 extractInfoFromHash = () => {
  if (!process.browser) {
    return undefined
  }
  const {id_token, state} = getQueryParams()
  return {token: id_token, secret: state}
}

export const setToken = (token) => {
  if (!process.browser) {
    return
  }
  window.localStorage.setItem('token', token)
  window.localStorage.setItem('user', JSON.stringify(jwtDecode(token)))
  Cookie.set('jwt', token)
}

export const unsetToken = () => {
  if (!process.browser) {
    return
  }
  window.localStorage.removeItem('token')
  window.localStorage.removeItem('user')
  window.localStorage.removeItem('secret')
  Cookie.remove('jwt')

  window.localStorage.setItem('logout', Date.now())
}

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

export const getUserFromLocalStorage = () => {
  const json = window.localStorage.user
  return json ? JSON.parse(json) : undefined
}

export const setSecret = (secret) => window.localStorage.setItem('secret', secret)

export const checkSecret = (secret) => window.localStorage.secret === secret

utils/lock.js


import { setSecret } from './auth'

import uuid from 'uuid'

const getLock = (options) => {
  const config = require('../config.json')
  const Auth0Lock = require('auth0-lock').default
  return new Auth0Lock(config.AUTH0_CLIENT_ID, config.AUTH0_CLIENT_DOMAIN, options)
}

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

const getOptions = (container) => {
  const secret = uuid.v4()
  setSecret(secret)
  return {
    container,
    closable: false,
    auth: {
      responseType: 'token',
      redirectUrl: `${getBaseUrl()}/auth/signed-in`,
      params: {
        scope: 'openid profile email',
        state: secret
      }
    }
  }
}

export const show = (container) => getLock(getOptions(container)).show()
export const logout = () => getLock().logout({ returnTo: getBaseUrl() })

pages/auth/sign-in.js


import React from 'react'

import defaultPage from '../../hocs/defaultPage'
import { show } from '../../utils/lock'

const CONTAINER_ID = 'put-lock-here'

class SignIn extends React.Component {
  componentDidMount () {
    show(CONTAINER_ID)
  }
  render () {
    return <div id={CONTAINER_ID} />
  }
}

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 { setToken, checkSecret, extractInfoFromHash } from '../../utils/auth'

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

  componentDidMount () {
    const {token, secret} = extractInfoFromHash()
    if (!checkSecret(secret) || !token) {
      console.error('Something happened with the Sign In request')
    }
    setToken(token)
    this.props.url.pushTo('/')
  }

  render () {
    return null
  }
}

Grab the token and secret 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: Nextjs exposes virtually everything to the client. Secrets and environment variables are leaked to the frontend. So if you want to perform an API call and you need to validate a token based on a secret, then you will have to run a custom express server so that your secret can be available only on the server. This also applies to other forms of operations that require loading some secret environment variables that the user of your app shouldn't have access to.

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.