NextScript — flexible script loading for Nextjs

Barry Steyn
EngineerApart Custom Software Engineering
4 min readAug 5, 2018

--

If you are a React engineer, unless you have been hiding under a rock, you know all about the amazing Next.js. Next’s killer feature is effortless server side rendering. It is also extremely customizable, but we found one thing lacking in terms of customization: The ability to fine-grain control the loading scripts on a page.

NOTE: This component requires a version of next that does not use webpack 4 (currently <= 6.1.1) since webpack 4 makes breaking changes. We will update this component to work with webpack 4 when a version of Next that uses it is released.

Why Is This A Problem?

By default, Next will load all the scripts on a page. This could be undesirable for the following reasons:

  1. Performance — some scripts could only be loaded when needed, or loaded after the page load event (especially for things like _error, if you know it is not needed immediately).
  2. Preloading — It is not uncommon to need to run a script — or load a script from a URL — before allowing your Next app to load.
  3. Polyfil preloading — This is the most common application of the previous point. Next bundles some polyfills, but only the ones NEXT requires — which may not be the ones you require. For example, if you require IntersectionObserver, it is on you to load it yourself. Since this is a HUGE polyfill (7kb minzipped!), you do not want to deliver it to all clients — only the ones that absolutely require it (IE <= 11, Safari ALL).

Introducing NextScript

We have named our project (not-so-cleverly) after the export of the same name in the Next.js framework. This component solves the problems posed above (and more!) in a flexible, minimal way. It compiles down to 2.3kb minzipped (although it is only ever executed on the server!) and has a simple API.

Built originally out of the Next’s NextScript component itself, all of Next’s original functionality is supported by default.

How It Works

The default NextScript implementation captures the scripts generated by the Next build process and immediately renders them to <script> tags in its render() function.

This component takes a different approach: It collects the same script information from the build that the original Next component does, but instead of immediately rendering it, it inserts the script information into a preloader script. This provides the opportunity to:

  • Load something else first.
  • Load something else after.
  • Load it on command.

After you’ve configured the component, it will execute your configurations inside the preloader, and then execute the Next scripts. If you don’t configure the component, it does exactly the same thing that the original Next component does!

Getting Started

Install from npm:

npm i @engineerapart/nextscript

# or

yarn add @engineerapart/nextscript

There is also an entry in NPM under nextscript. This is our placeholder to avoid confusion with potential package collisions and is not the published package.

Opt-In

All additional functionality of this component is opt-in. You can configure the component to behave exactly as NextScript itself does, or you can optionally enable some of the additional functionality. Simply rendering <NextScript /> will produce identical behavior to the component found in Next.

Examples

Duplicate The Default Functionality Of Next.Js

The only difference between this example and the default Next _document is where NextScript is imported from:

import { NextScript } from '@engineerapart/nextscript';
import Document, { Head, Main } from 'next/document';
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx);
return { ...initialProps };
}
render() {
return (
<html>
<Head>
<style>{`body { margin: 0 } /* custom! */`}</style>
</Head>
<body className="custom_class">
<Main />
<NextScript />
</body>
</html>
);
}
}

Preload Polyfills

The canonical example: How to preload any polyfill, including polyfills not defined by this project:

import { NextScript, FeaturePolyfills } from '@engineerapart/nextscript';
import Document, { Head, Main } from 'next/document';

const features = [
FeaturePolyfills.FETCH, // ensures ES6 fetch is available
FeaturePolyfills.CUSTOMEVENT, // ensures custom event is available
{ // example of how to add any other type of polyfill
test: `('entries' in Array.prototype)`,
feature: 'Array.prototype.entries',
},
];

export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx);
return { ...initialProps };
}

render() {
return (
<html>
<Head></Head>
<body className="custom_class">
<Main />
<NextScript features={features} />
</body>
</html>
);
}
}

Our NextScript component points to the Polyfill.io service, which serves ~150 million polyfill requests per day (4.5 billion/month!). However, eventually this project will also support pointing to your own self-hosted polyfill service, provided that it adheres to the Polyfill.io API (instructions for self-hosting the Polyfill.io service can be found in their repository).

Conclusion

This component has become a lifesaver for us, and we feel it may be useful for you as well. We spent a lot of time on this, feedback would be welcome. Here are some links to get you started:

--

--