TL;DR: Next.js, a lightweight framework for static and server-rendered applications is popular for building Universal JavaScript applications. In this article, I'll cover the features of Next.js 6.


A Refresher: What is a Universal JavaScript Application?

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. Check out the excellent blog post on Universal JavaScript written by Michael Jackson.

Introduction to Next.js 6 Features

1. Zero Config Static Exports

Before now, Next.js required the use of a config file, next.config.js, in your application to manually set up routes before you can export the static version of your app.

With Next.js 6, modifications to next.config.js file is no longer needed unless there's a really advanced custom routing needed. The route map is automatically generated from the files in the pages/ directory.

Just simply run the export command:

next build && next export

and the fully functional static version of your app will be made available.

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

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

With the command in place, you can simply run npm run export and it will build your Next.js app as a static website.

2. Babel 7 Support

Next.js now runs on Babel 7. Babel provides a way for developers to use the latest JavaScript syntax while allowing them to not worry about how to make it backward compatible for their users by translating it.

Babel 7 is faster and ships with a lot of new features such as:

  1. More options for minification.
  2. JSX Fragments.
  3. A new upgrade tool, babel-upgrade that tries to automatically make upgrade changes.
  4. Implementation of several TC39 proposals.

3. New JSX Fragment Syntax Support

In React 16, Fragments are written like this:

render() {
  return <React.Fragment>
    <Car />,
    <Truck />
  </React.Fragment>
}

Next.js supports a new JSX Fragment syntax, <>, for creating fragments.

render() {
  return <>
    <Car />,
    <Truck />
  </>
}

4. _app.js Inclusion

Before now, Next.js shipped with the ability to include _document.js for extensibility. However, there were limitations using it because it was mostly limited to the initial pre-rendering phrase.

In Next.js 6, a new extension file, _app.js, can be added to a Next.js 6 app within the pages directory to provide better extensibility that allows hooking into the runtime lifecycle and data fetching. Check out some case studies it enables:

  • Better Error handling––In Next.js, you can use the React componentDidCatch component method to handle exceptions. However, the logic is best placed in _app.js. Check out the example below:

pages/app.js_

import App from 'next/app'

export default class MyApp extends App {
  componentDidCatch (error, errorInfo) {
    console.log('CUSTOM ERROR HANDLING', error)
    // This is needed to render errors correctly in development / production
    super.componentDidCatch(error, errorInfo)
  }
}
  • Page Transitions––Smooth animations are possible on the client side and it's possible via the use of _app.js. Check out the next version of page transitions app.

  • Better Integrations from third-party tools––With _app.js, it's easy to integrate state management frameworks like Apollo and Redux.

5. TypeScript Support

Babel 7 now has built-in support for TypeScript. And Next.js now uses Babel 7. Therefore, Next.js 6 has a first-class Typescript support.

Note: Install the latest version of https://github.com/zeit/next-plugins/tree/master/packages/next-typescript/#readme.

6. Support for Nested .babelrc

In Next.js 6, you can now include a scoped .babelrc file in a nested directory that requires a different Babel configuration.

src/
    .babelrc      # General .babelrc
  components/
    i18n/
      .babelrc  # This .babelrc only applies to this directory

Securing a Next.js 6 App with Auth0

I'll use the app from our previous Next.js article. Obviously, a lot of things have changed since Next.js 3. Therefore, we'll make a lot of changes step-by-step. Check out the upgraded Next.js 6 app on GitHub if you want to dive straight into the code.

We can secure a Next.js app with Auth0 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 Application on 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 and port you are running on. Furthermore, set the Allowed Logout URLs to http://localhost:3000/.

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

We have put the necessary things in place to secure our app. Now, let's make the changes required for the app to run on Next.js 6 before we test our authentication toolkit.

  1. Re-install next, react-dom, react, babel-plugin-styled-components, and styled-components to have the latest respective package versions.

  2. Update .babelrc, modifying the name of the registered plugin to babel-plugin-styled-components like so:

     {
       "presets": [
         "next/babel"
       ],
       "plugins": [
         [
           "babel-plugin-styled-components",
           {
             "ssr": true,
             "displayName": false
           }
         ]
       ]
     }
    
  3. React.PropTypes has moved into a different package since React v15.5. Go ahead and install the new prop-types library.

       npm install prop-types
    
  4. Update every file that imports PropTypes from React to import PropTypes from the new library like this:

     ...
     // Before
     import React, { PropTypes } from 'react'
    
     // Now
     import React from 'react'
     import PropTypes from 'prop-types'
     ...
    
  5. In /hocs/defaultPage.js, there's a block code of code that ensures our page is styled immediately it server-renders. Without this block of code, a flash of unstyled page is shown to the user everytime the page is reloaded.

     ...
     {!process.browser && (
       <style>
         {styleSheet.getCSS()}
       </style>
     )}
     ...
    

Delete the code. We'll take advantage of _document.js to implement style flushing for server-side rendering. Now, create a new _document.js file in the pages directory and add the following code:

import Document, { Head, Main, NextScript } from 'next/document'
import { ServerStyleSheet } from 'styled-components'

export default class MyDocument extends Document {
  static getInitialProps ({ renderPage }) {
    const sheet = new ServerStyleSheet()
    const page = renderPage(App => props => sheet.collectStyles(<App {...props} />))
    const styleTags = sheet.getStyleElement()
    return { ...page, styleTags }
  }

  render () {
    return (
      <html>
        <Head>
          <title>Next.js + Auth0</title>
          {this.props.styleTags}
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </html>
    )
  }
}

The code above enables server side rendering of the styles. It does two things:

  1. Hook into the renderPage method to parse the child component’s styles server-side.
  2. It hooks into the Document on the initial page load so that the app can update the <head> with the <style> tags that are required by the components.

Authentication Process

The code shown in this section already exists in the repo. It's presented here again purely for explanation.

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')
}

The code above sets and unsets a cookie with the token gotten from the Auth0 server.

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)

The code above makes a request to Auth0 server to initiate and authorize a user.

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.

Auth0 Sign in page example 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.

Auth0 authenticated - signed in example 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 whether 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.

Auth0 secret page - gated content - unauthorized view 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

Next.js 7.0 is in the works and it promises to be better. nextjs.org codebase is now open-source. A lot can be learned about using Next.js by going through the code extensively. There are also lots of integration examples for you to be able to use Next.js with several tools in the JavaScript ecosystem.

Finally, you can learn Next.js from scratch via the official learn site.