Created
January 14, 2021 12:32
-
-
Save gabeidx/06cec39909b3bd5714575eac97f66e7b to your computer and use it in GitHub Desktop.
NextJS custom _document
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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