5 Tips For Using NextJs

June 1, 2018

NextJs is a framework on top of React. Some features of NextJs include:

  • Automatic routing. For example, add the file about.js to the pages subdirectory and it becomes available as a route at http://localhost:3000/about.
  • Server-side rendering. This is arguably the biggest selling point of NextJs.
  • Zero-configuration. No need to configure Webpack for development and production. But when you're ready you can add a next.config.js to extend or override NextJs' default configuration.

It can take some time to get hang of the "Next" way of doing things. Here are some things that I wish I knew when I first got started with Next.

Using <Link> Correctly

One of the merits of NextJs is code-splitting and prefetching. Code-splitting means that if the user visits one page they don't need to download the entire app bundle. Prefetching let's the user navigate around your app without inducing a page refresh which makes the app seem less snappy. On a traditional website you click a link, navigate to a new page, and the page refreshes.

With Next you can pass the prefetch prop to . When the user clicks the URI may change in the address bar but the page doesn't reload.

While Next's prefetch feature is great it can be confusing to use at first.

Let's say you have a post at /posts/my-post-slug and you want to link to it. The key is to realize that NextJs prefers query strings to pretty URIs. Next will always prefer /posts/id?=post-slug. In the Link as prop you specify the pretty URI, but then you also need to tell the Link component the query string version with href.

import Link from 'next/Link'
const Btn = ({post}) => ( 
  <div>
    <Link
      prefetch
      href={{ pathname: '/posts', query: { id: post.slug } }}
      as={`/${post.slug}/`}
    >
      Next
    </Link>
  </div>
)
export default Btn

server.js:

// server.js
...
server.get('posts/:id, (req, res) => {
  // Next defaults to using query strings, e.g., /posts?id=slug instead of posts/slug
  // Therefore we merge req.params and req.query and make it accessible to pages/posts.js.
  const query = Object.assign({}, req.params, req.query)
  return app.render(req, res, '/posts', query)
})

pages/posts.js

pages/posts.js
...
class Posts extends React.Component {
  // getInitialProps() runs on both the server or the client depending on the situation.
  static async getInitialProps(ctx) {
     const {req, query} = ctx
     return query
  }
  render() {
    return(
     <div></div>
    )
  }
}

Use Next's Link To Serve Global Stylesheets

Sometimes you don't feel like using the latest and greatest way to style React components. You just want to use a static, global stylesheet. Maybe SASS. To accomplish this, see the following code snippet below. We use Next's Head component.

In production, we include a standard link to the stylesheet. But in development we use dangerouslySetInnerHTML to inline the css.

The following snippet is a component that you'd wrap around your page components.

// components/Layout.js
import Head from 'next/head'
import inlineCSS from '../css/main.scss'
class Layout extends React.Component {
  render() {
  let stylesheet
    if (process.env.NODE_ENV === 'production') {
      stylesheet = <link rel="stylesheet" type="text/css" href="/assets/main.css" />
    } else {
      stylesheet = <style dangerouslySetInnerHTML={{ __html: inlineCSS }} />
    }
    return(
      <div>
       <Head>
         <title></title>
         <meta name="viewport" content="width=device-width, initial-scale=1" />
          {stylesheet}
       </Head>
       {this.props.children}
      </div>
    )
  }
}

Then on the server (assuming you're using Express):

// server.js
const sass = require('node-sass')
const path = require('path')
const fs = require('fs')
const sassResult = sass.renderSync({
  file: path.resolve(__dirname, '../../source/css/main.scss'),
  outputStyle: 'compressed',
})
 server.get('/assets/main.css', (req, res) => {
    res.setHeader('Content-Type', 'text/css')
    res.setHeader('Cache-Control', 'public, max-age=2592000')
    res.setHeader('Expires', new Date(Date.now() + 2592000000).toUTCString())
    res.send(sassResult.css)
 })

In next.config.js:

// next.config.js
module.exports = {
  webpack: (config, { dev }) => {
    config.module.rules.push(
      {
        test: /\.(css|scss)/,
        loader: 'emit-file-loader',
        options: {
          name: 'dist/[path][name].[ext]',
        },
      },
      {
        test: /\.css$/,
        loader: 'babel-loader!raw-loader',
      },
      {
        test: /\.scss$/,
        loader: 'babel-loader!raw-loader!sass-loader',
      },
    )
    if (config.resolve.alias) {
      delete config.resolve.alias.react
      delete config.resolve.alias['react-dom']
    }
    return config
  },
}