Skip to content

Instantly share code, notes, and snippets.

@gabeidx
Created January 14, 2021 12:32
Show Gist options
  • Save gabeidx/06cec39909b3bd5714575eac97f66e7b to your computer and use it in GitHub Desktop.
Save gabeidx/06cec39909b3bd5714575eac97f66e7b to your computer and use it in GitHub Desktop.
NextJS custom _document
import { default as React } from 'react';
import { default as Document, Html, Head, Main, NextScript } from 'next/document';
// We need to duplicate the type definition for `DocumentFiles` here because nextjs
// does not export it from 'next/document'
type DocumentFiles = {
sharedFiles: readonly string[];
pageFiles: readonly string[];
allFiles: readonly string[];
};
class OptimizedHead extends Head {
getPreloadMainLinks(): JSX.Element[] | null {
// Don't preload main links
return null;
}
getPreloadDynamicChunks(): (JSX.Element | null)[] {
// Don't preload dynamic chunks
return [null];
}
}
class OptimizedNextScript extends NextScript {
getScripts(files: DocumentFiles): JSX.Element[] {
const { assetPrefix, buildManifest, isDevelopment } = this.context;
// Skip this entire logic if we are on dev
if (isDevelopment) {
return NextScript.prototype.getScripts.call(this, files);
}
// Events considered as "minimal user interaction"
const events = ['focus', 'scrollstart', 'touchstart', 'mousemove'];
const deferred: string[] = [];
const immediate: string[] = [];
const normalScripts = files.allFiles.filter((file) => file.endsWith('.js'));
const lowPriorityScripts = buildManifest.lowPriorityFiles?.filter((file) => file.endsWith('.js'));
[...normalScripts, ...lowPriorityScripts].map((file) => {
const filename = encodeURI(file);
const fullpath = `${assetPrefix}/_next/${filename}`;
// Separate some scripts to be loaded immediately
if (/(main|framework|common|webpack|namespace|app)/.test(file)) {
immediate.push(fullpath);
} else {
deferred.push(fullpath);
}
});
return [
// Immediate scripts get their own `<script>` tag and fire off asap
...immediate.map((script) => (
<script
key={script}
src={script}
async
nonce={this.props.nonce}
crossOrigin={this.props.crossOrigin || process.env.__NEXT_CROSS_ORIGIN}
></script>
)),
// Deferred scripts are bundled into a single function that is triggered by
// any of the "minimal user interaction" events
<script
key={`deferredScripts`}
async
nonce={this.props.nonce}
crossOrigin={this.props.crossOrigin || process.env.__NEXT_CROSS_ORIGIN}
dangerouslySetInnerHTML={{
__html: `
function loadDeferredScripts() {
${deferred
.map((script, i) => {
return `
const script${i} = globalThis.document.createElement('script'); \
script${i}.type = 'text/javascript'; \
script${i}.async = true; \
script${i}.src = '${script}'; \
globalThis.document.getElementsByTagName('head')[0].appendChild(script${i}); \
`;
})
.join('')}
}
${events
.map((event) => {
return `globalThis.document.addEventListener('${event}', loadDeferredScripts, { once: true });\n`;
})
.join('')}
`
}}
/>
];
}
}
export default class OptimizedDocument extends Document {
render(): JSX.Element {
return (
<Html>
<OptimizedHead />
<body>
<Main />
<OptimizedNextScript />
</body>
</Html>
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment