Things I've Learned Building Next.js Apps

JW

Jonathan Wong / February 15, 2019

6 min read––– views

Next.js

I've spent a lot of time in the past 4 months creating Next.js apps for both work and personal use.

Along that journey, I've grown to really ❤ Next.js and it's ecosystem. Here are some of the things I've learned along the way.

CSS-In-JS#

I used to be strongly against CSS-in-JS. If you didn't like CSS, then use SASS/LESS. Why mix styling with your code?

Well, turns out I was wrong. Styling is code and I have never felt more productive styling components than I do using CSS-in-JS. Next.js comes preloaded with styled-jsx, which is fine, but I prefer styled-components 💅

If you want learn more, check out Building a UI Component Library with Styled Components.

Font Loading#

Performant font loading is much, much more difficult than I thought. I'm immensely grateful to Zach Leatherman for his Comprehensive Guide on Web Fonts. One approach I've taken is to use @font-face in combination with the font-display attribute.

This approach allows me prevent FOUT (Flash of Unstyled Text) but still has FOIT (Flash of Invisible Text). Yes, FOIT is undeserable, but thanks to Next.js my Lighthouse performance score is still almost 100.

@font-face {
  font-family: 'Fira Sans';
  src: url('/fonts/FiraSans-Bold.ttf');
  font-weight: bold;
  font-display: auto;
  font-style: normal;
}

Another approach I've used with Google Fonts is to include the link in the Head of my document.

pages/_document.js
import Document, { Head, Main, NextScript } from 'next/document';
import React from 'react';

class CustomDocument extends Document {
  render() {
    return (
      <html lang="en">
        <Head>
          <link
            href="https://fonts.googleapis.com/css?family=Fira+Sans:400,400i,700&display=swap"
            rel="stylesheet"
          />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </html>
    );
  }
}

export default CustomDocument;

Becoming informed about all the different options and their tradeoffs has been a worthy investment.

Dynamic Imports and Testing#

It's likely that at some point in scaling your Next.js app, you'll want to use an external package that doesn't work well server-side rendered. For me, this package was react-select.

When SSR this component, it simply did not work in Safari. Until that bug is fixed, I needed a workaround. Thanks to Next's Dynamic Imports, it's easy to import a component and disable SSR.

import dynamic from 'next/dynamic';

const ReactSelectNoSSR = dynamic(() => import('../components/select'), {
  ssr: false
});

export default () => (
  <>
    <Header />
    <ReactSelectNoSSR />
    <Footer />
  </>
);

While this works, we can go a step further and provide a loading placeholder to make the user experience better.

const ReactSelectNoSSR = dynamic(() => import('../components/select'), {
  loading: () => <Input />,
  ssr: false
});

Much better 🎉 Now, how do we test it?

My preferred testing library is Jest. The dynamic import support offered by Next.js does not expose a way to preload the dynamically imported components in Jest’s environment. However, thanks to jest-next-dynamic, we can render the full component tree instead of the loading placeholder. Perfect!

You'll need to add babel-plugin-dynamic-import-node to your .babelrc like so.

.babelrc
{
  "plugins": ["babel-plugin-dynamic-import-node"]
}

Then, you can use preloadAll() to render the component instead of the loading placeholder.

import preloadAll from 'jest-next-dynamic';
import ReactSelect from './select';

beforeAll(async () => {
  await preloadAll();
});

MDX#

My portfolio has transformed quite a bit since I started it in 2014. Originally, it was just static HTML & CSS. Then, I switched over to Hugo so I could write my posts in Markdown. This worked well for a while, but I wanted full control over my layouts and felt much more comfortable with JavaScript. That's what led me to Next.js and MDX.

With MDX, I can use JSX components inside my Markdown documents with ease. This way, if I want to do something custom, it's as simple as importing a React component. I've also started writing my presentations with MDX and code-surfer 🏄

For maximum performance, you can use the prefetch attribute on Next's <Link> component. This will give the perception the page loads instantly. As of Next.js 8, prefetch uses <link rel="preload"> instead of a <script> tag. It also only starts prefetching after onload to allow the browser to manage resources.

<Link prefetch href="/">
  <a>Home</a>
</Link>

Update: Next.js 9 will automatically prefetch <Link> components as they appear in-viewport. You no longer need to explicitly say prefetch.

Polyfills#

Next.js supports IE10 and all modern browsers out of the box using @babel/preset-env. However, it's possible that your own code or external NPM dependencies might use features not supported in your target browsers. In this case, you will need to add polyfills. This requires changes in a couple of places.

First, you will need to create/override your next.config.js file to customize Webpack.

next.config.js
module.exports = {
  webpack: function (cfg) {
    const originalEntry = cfg.entry;
    cfg.entry = async () => {
      const entries = await originalEntry();

      if (
        entries['main.js'] &&
        !entries['main.js'].includes('./client/polyfills.js')
      ) {
        entries['main.js'].unshift('./client/polyfills.js');
      }

      return entries;
    };

    return cfg;
  }
};

Then, create /client/polyfills.js and include whichever polyfills you need.

import '@babel/polyfill';

Vercel#

Vercel is hands down the easiest way to deploy applications I've ever used. It's incredibly easy to get started and their GitHub integration will automatically deploy your app on pull requests and leave a link for you to review the changes. If everything looks good, it will deploy to prod when the PR is merged. Simple as that.

The icing on the cake is how well the Vercel ecosystem works together. Their domain service allows you to buy domains from the command line. I've never been able to go from an idea to a live, deployed application hosted on a domain so fast.

Bonus: I was even able to setup email forwarding for my domain and create me@leerob.io using improvmx 🎉

Where to Get Help#

The Next.js and Vercel community support is fantastic. They're very responsive to emails, issues, or any other form of contact. My preferred method would be GitHub Discussions. Thanks to Discussions, all questions and answers are indexed, searchable, and easy to find. Their tutorials and docs are also fantastic and well-written.

Additional Resources#

Discuss on TwitterEdit on GitHub
Spotify album cover

/tools/photos