Remove Next.js client-side JavaScript
02 Juin 2020
It’s the first time I try to write a post in english, so please keep that in mind if you find errors. Help me to get better with a little message on Twitter or in the comments.
Important notes:
There is an experimental option that can help you reach that same goal, see this tweet.
See more on this MR on github
Finally, the solution described in this post might not be future proof, see this other tweet
I recently migrated a website that was built with Hugo Static Site Generator to Next.js. It’s a simple website, just basic HTML, it doesn’t need JavaScript, so I wanted to have the simplest and cleanest HTML possible.
What’s the problem with Next.js HTML?
In my case I really wanted a true static build, just plain HTML files.
By default Next.js has an “export» feature that allows to generate HTML files when possible but it adds a lot of boilerplate in the process to keep Next.js features once the page is loaded. Here is what Next.js outputs for a page with the default configuration:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width"/>
<meta charSet="utf-8"/>
<title>Create Next App</title>
<meta name="next-head-count" content="3"/>
<link rel="preload" href="/_next/static/css/86e2a6c40098b108532d.css" as="style"/>
<link rel="stylesheet" href="/_next/static/css/86e2a6c40098b108532d.css"/>
<link rel="preload" href="/_next/static/jm8-FDNbv6uahuS0Stt5z/pages/index.js" as="script"/>
<link rel="preload" href="/_next/static/jm8-FDNbv6uahuS0Stt5z/pages/_app.js" as="script"/>
<link rel="preload" href="/_next/static/runtime/webpack-b65cab0b00afd201cbda.js" as="script"/>
<link rel="preload" href="/_next/static/chunks/framework.0f140d5eb2070c7e423d.js" as="script"/>
<link rel="preload" href="/_next/static/chunks/24c7269755b1d5ac4539720791aeadbab8599c4f.1a6c8dffd44e2e87166e.js" as="script"/>
<link rel="preload" href="/_next/static/runtime/main-40338d0a87bad8a900d5.js" as="script"/>
</head>
<body>
<div id="__next">
<main>
<h1>
Welcome to
<a href="https://nextjs.org">Next.js!</a>
</h1>
</main>
</div>
<script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{}},"page":"/","query":{},"buildId":"jm8-FDNbv6uahuS0Stt5z","nextExport":true,"autoExport":true,"isFallback":false}</script>
<script nomodule="" src="/_next/static/runtime/polyfills-ee4ee5083d7312778722.js"></script>
<script async="" data-next-page="/" src="/_next/static/jm8-FDNbv6uahuS0Stt5z/pages/index.js"></script>
<script async="" data-next-page="/_app" src="/_next/static/jm8-FDNbv6uahuS0Stt5z/pages/_app.js"></script>
<script src="/_next/static/runtime/webpack-b65cab0b00afd201cbda.js" async=""></script>
<script src="/_next/static/chunks/framework.0f140d5eb2070c7e423d.js" async=""></script>
<script src="/_next/static/chunks/24c7269755b1d5ac4539720791aeadbab8599c4f.1a6c8dffd44e2e87166e.js" async=""></script>
<script src="/_next/static/runtime/main-40338d0a87bad8a900d5.js" async=""></script>
<script src="/_next/static/jm8-FDNbv6uahuS0Stt5z/_buildManifest.js" async=""></script>
<script src="/_next/static/jm8-FDNbv6uahuS0Stt5z/_ssgManifest.js" async=""></script>
</body>
</html>
That’s way too much HTML for my goal, I needed to find a way to trim that.
My solution
After reading the docs, searching on Next.js community and some tests, here is what I found as a solution: use a Custom Document and only export what I need. First, I had to create a file in ./pages/_document.js
and added this:
import Document, { Html, Head, Main, NextScript } from "next/document";
class HeadProduction extends Head {
render() {
const { head } = this.context._documentProps;
const children = this.props.children;
return (
<head {...this.props}>
{children}
{head}
{this.getCssLinks()}
</head>
);
}
}
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx);
return { ...initialProps };
}
render() {
const isDev = process.env.NODE_ENV === "development";
return (
<Html>
{isDev ? <Head /> : <HeadProduction />}
<body>
<Main />
{isDev && <NextScript />}
</body>
</Html>
);
}
}
export default MyDocument;
With these components, I had 2 behaviours.
While developping, you can still take advantage of Next.js that’s why it uses the default <Head />
and <NextScript />
components. Keeping these in development is a good idea, because you will still enjoy Next.js hot-reload and server indicators for example.
But when building for production it uses the customized <HeadProduction/>
component and removes <NextScript />
. The component <HeadProduction />
will handle the props and create links for stylesheets, that’s all. Thereby we will still be able to set the page title in other pages and Next.js won’t inject any scripts.
The result
In the end, after building and exporting I get the following HTML for the same page:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width"/>
<meta charSet="utf-8"/>
<title>Create Next App</title>
<link rel="preload" href="/_next/static/css/86e2a6c40098b108532d.css" as="style"/>
<link rel="stylesheet" href="/_next/static/css/86e2a6c40098b108532d.css"/>
</head>
<body>
<div id="__next">
<main>
<h1>
Welcome to <a href="https://nextjs.org">Next.js!</a>
</h1>
</main>
</div>
</body>
</html>
That’s way better isn’t it ? And my goal is reached, it reduces the number of line by almost 2 and the weight of the page from 2.1 kbytes to 434 bytes. Awesome !
Now every page navigation will reload the page but as it’s just HTML it’s blazing fast.
Conclusion
With this little trick I can use Next.js to build my website with React components and export it as simple HTML website as real Static Site Generator. If I need to add some JavaScript all I have to do is restore Next.js default component and I’ll get heavy HTML but I’ll be more inclined to accept it.
Finally I must mention the blog of Daniel Stocks and his article on
Server-side Only React with Next, that was a good starting point on what can be done with Custom Document. If you read his post, you’ll also find a way to remove the <div id="next" />
around the content.
Big thanks to François for his review.