Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Build Better Universal JavaScript Apps With Next.js 2.0

DZone's Guide to

Build Better Universal JavaScript Apps With Next.js 2.0

The dev world seemingly abounds with JavaScript frameworks, but they're not always great. Check out the latest upgrades to an interesting, young JS framework, Next.js

· Web Dev Zone
Free Resource

Get deep insight into Node.js applications with real-time metrics, CPU profiling, and heap snapshots with N|Solid from NodeSource. Learn more.

What Is a Universal JavaScript Application?

Just to 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 2.0?

Next.js 2.0 was released on March 27, 2017. It comes bundled with a set of new features and improvements to existing features. Next.js 1.0 already included the following features:

  • Hot code reloading.
  • Automatic transpilation and bundling (using babel and webpack).
  • Server rendering and indexing of JavaScript files in the /pages directory.
  • Serving of static files.
  • CSS isolation and modularization.

So, what has changed? What improvements have been made? Are there new features? Will your Next.js apps build with this new release be truly production ready? Let's go through everything that Next.js 2.0 has to offer.

1. Faster Compilation Times

Every developer and their dog need their apps to be highly performant from development stage up until production. A lot of work has been done to improve the dev build time that Next.js 1.0 brought to the scene. With great joy, I hereby announce to you that Next.js 2.0 comes bundled with shorter build/rebuild times. This was made possible by offering Lazy compilation during development. This simply means that before now, when you ran Next, it compiled all the pages. But now, lazy compilation ensures that it is only when a user hits a page that compilation happens. So each page that is called by the user is an on-demand entry.

Lazy Compilation during developmentLazy Compilation during development.

Implement on-demand entriesImplement on-demand entries.

2. Ahead-Of-Time Gzip Compression

Next.js 2.0 automatically gzips all your static JavaScript files and serves them when you build your app by running next build. This saves a lot of CPU power, especially for apps deployed in cloud function-like environments.

3. Smaller and Efficient Builds

Apart from reducing the dev build times, Next.js 2.0 offers much smaller and more-efficient builds than its' previous version. So by default, your app size is now smaller.

Bundle sizes for 1.0 and 2Basic site with Next.js and React bundled.

Source: zeit.co

Page loads have also been made faster by making it so that initial bundle files are cached permanently on clients.

An issue was raised in Next.js 1.0 about shared dependencies between pages that caused latency when browsing through different server-rendered pages, and the ever-ready team found a way to solve it by setting up Webpack common chunks to avoid shipping repeated code across components.

Shared dependency issuesShared dependency issues.

Shared dependency issues solvedDependency issue solved with CommonsChunkPlugin.

4. Programmatic API for Routing

In the first major version of Next.js, dynamic routing was only possible with query strings. There was no way to achieve clean and fancy URLs, and loading your own custom server code was a big challenge. With Next.js 2, those challenges are a thing of the past.

Custom routingIssue raised last year.

You can now write your own custom server programmatically, customize routes, and use different route patterns like so:

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

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

app.prepare().then(() => {

    const server = express()

    server.get('/p/:username', (req, res) => {
        return app.render(req, res, '/z', {
            ...req.query,
            username: req.params.username
        })
    })

    server.get('*', handle)
    server.listen(3333)
})

In the code sample above, you have set up a custom server. The path /p/prosper where prosper is the :username is resolved to ./pages/z. The page gets access to the username parameter and can do whatever has been programmed iintoit. Check out this example.

5. Pre-Fetching Pages

Next.js 2 comes bundled with an API that allows you to prefetch pages. Any <Link> tag can accept a prefetch prop and prefetch the pages it links to in the background, like so:

import Link from 'next/link'

export default () => (
  <nav>
    <ul>
      <li><Link prefetch href='/pricing'><a>About</a></Link></li>
      <li><Link prefetch href='/auth0'><a>Contact</a></Link></li>
    </ul>
  </nav>
)

This gives you the performance of an SPA coupled with server rendering. Wow, that's better performance with little effort. Whoop! Whoop!

6. Immutable Caching

When you build your app with next build and start your app, Next.js 2 will serve your JavaScript files and other assets as immutable assets. This simply means that once the browser has downloaded any immutable asset, if you reload the browser page, your browser won't try to load these assets from the server again.

Another gain for performance and speed. Whoop! Whoop!

7. Custom Babel and Webpack Configurations

Next.js 2 is fully extensible. You have complete control over Babel's and Webpack's configuration. For example, if you want to extend Babel, you can simply define a .babelrc file at your app's root and apply the next/babel preset. With that, you include whatever Babel plugins you need, like so:

{
  "presets": ["next/babel"],
  "plugins": ["transform-flow-strip-types"]
}

Check out this working example.

To extend the usage of Webpack in Next.js, you can create a next.config.jsfile in the root of your project's directory. Once you have that, you can define a function in the Node.js module like so:

module.exports = {
  webpack: (config, { dev }) => {
    // Perform customizations to config

    // Important: return the modified config
    return config
  }
}

Note: The next.config.js file is a regular Node.js module.

8. Composed CSS Support

Before now, next/css was the default CSS-in-JS solution for Next.js. In Next.js 2, it has been deprecated in favor of styled-jsx, a Babel transformation that provides full, scoped, and component-friendly CSS support for JSX (rendered on the server or the client).

export default () => (
  <div>
    <textarea />
    <button>add comment</button>
    <style jsx>{`
      textarea {
        width: 400px;
        height: 100px;
        display: block;
        margin-bottom: 10px;
      }

      button {
        padding: 3px 4px;
      }
      @media (max-width: 750px) {
        textarea {
          width: 100%;
        }
      }
    `}</style>
  </div>
)

In the code example above, you can see how it provides scoped support for this JSX-written component.

9. Isolating React From Next

Before now, Next.js shipped with React. All you needed to do was:

npm install next --save

In Next.js 2.0, you now need to bring in next with react and react-domlike so:

npm install --save next react react-dom

This creates opportunity for you to use other React API implementations such as Preact. It also allows you to update React independently of Next.js.

9. Practical Examples of Backend Integrations

Many developers have been helping out in providing examples on how to integrate Next.js with several backend technologies.

10. Availability of Learning Platforms

It's amazing to see that in a short time that Next.js has been in existence, lots of examples have been amassed and there is a learning platform that the Next.js team approves of. With the release of Next.js 2, we have:

Learn Next.jslearnnextjs.com Landing page

Logged In viewlearnnextjs.com Logged in view

Oh, the UI and Backend for learnnextjs.com is open-source. This presents another opportunity to learn Next.js 2.0 by going through its source code.

Enter Next.js 2.2.0

Next.js 2.2.0 was tagged recently. It comes bundled with some nice changes:

  • CDN support: You might want to upload all your static files to a CDN, including build files. Now, you can serve Next.js static assets via a CDN. All you need is to expose the following option in next.config.js like so:
const isProd = process.NODE_ENV === 'production'
module.exports = {
  // You may only need to add assetPrefix in the production.
  assetPrefix: isProd? 'https://cdn.mydomain.com' : ''
}

More information can be found here.

  • ETag support for server rendered pages : The ETag HTTP response header is an identifier for a specific version of a resource found at a URL. It allows caches to be more efficient, and saves bandwidth, as a web server does not need to send a full response if the content has not changed. However, if the content has changed, etags are useful to help prevent simultaneous updates of a resource from overwriting each other. Etags are otherwise known as fingerprints used for tracking resource changes on the server.

In Next.js, all server-rendered pages now support Etags.

More information can be found in the release notes.

Aside: Authenticating a Next.js 2.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.

Note: Make sure you set the Allowed Callback URLs to http://localhost:3000/ or whatever url/port you are running on. Also set the Allowed Origins (CORS) to http://localhost:3000/ or whatever domain url you are using, especially if it is hosted.

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.

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 inSign-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 inSecret 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 unauthorizedNot displaying valid content because the user can't access the secret page without signing in.

Note: Next.js 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.

Conclusion

With Next.js 2, the Github repo now has over 11,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 lovely tool and the JavaScript community for their continuous support. In fact, they already have plans for Next.js 3.

Try out Next.js 2 and let me know what you think in the comments section!

Node.js application metrics sent directly to any statsd-compliant system. Get N|Solid

Topics:
web dev ,javascript ,framework

Published at DZone with permission of Prosper Otemuyiwa, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}