After.js Assets
The Problem with After.js
If you ever tried to build an Server Side Renderd App with After.js you well notice that when you code spilit your components, After.js only send the main css and js files then in client side in ensureReady
method tries to fetch javascript and css files. it's too slow (even Google PageSpeed Insights warns about it)
After.js Assets solves problem by adding a plugin to razzle and call a fucntion in custom Document file
Setup
First Install afterjs-assets:
yarn add afterjs-assets
then create a razzle.config.js
file in root of project (next to the package.json
) and put this content inside it:
// razzle.config.js moduleexports = plugins: "manifest"
this will load razzle-plugin-manifest, this plugin will generate manifest.json
file next to the server.js file in build folder. (we will use this file in next steps).
checkout it's repo for more options and configuration.
routes
Add name and chunkName to// ./src/routes.js // normal route name: "HomePage" // pick a random name path: "/" exact: true component: Home // codesplit route name: "AboutUs" // pick a random name path: "/about" exact: true component:
Note 1: name
must be uniqe and all routes must have a name with uniqe value
.
Note 2: we used webpackChunkName with dynamic import, chunk name Must be as same as name property.
name: "AboutUs" path: '/about' component: name: "Shop" path: '/shop' component:
<Document>
Create Custom Based on After.js Guide create a file in ./src/Document.js like so:
// ./src/Document.js Component static async { const page = await return assets data ...page } { const helmet assets data = thisprops // get attributes from React Helmet const htmlAttrs = helmethtmlAttributes const bodyAttrs = helmetbodyAttributes return <html ...htmlAttrs> <head> <meta httpEquiv="X-UA-Compatible" content="IE=edge" /> <meta charSet="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> helmettitle helmetmeta helmetlink assetsclientcss && <link rel="stylesheet" href=assetsclientcss /> </head> <body ...bodyAttrs> <AfterRoot /> <AfterData data=data /> <script type="text/javascript" src=assetsclientjs defer crossOrigin="anonymous" /> </body> </html> }
Then import getAssets
from afterjs-assets
and call it in static getInitialProps
:
Note: manifest.json
will get generated at build time so don't worry if it's not already exist.
// ./src/Document.js ;; const prefix = processenvNODE_ENV === "production" ? "/" : `http://:/` Component static async { const page = await ; // call getAssests and pass styles and scripts to component const styles scripts = ; return assets data styles scripts ...page ; }
then in render
method just loop through scripts and styles
{ const helmet assets data = thisprops; const htmlAttrs = helmethtmlAttributes; const bodyAttrs = helmetbodyAttributes; return <html ...htmlAttrs> <head> <meta httpEquiv="X-UA-Compatible" content="IE=edge" /> <meta charSet="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> helmettitle helmetmeta helmetlink assetsclientcss && <link rel="stylesheet" href=assetsclientcss /> styles </head> <body ...bodyAttrs> <AfterRoot /> <AfterData data=data /> <script type="text/javascript" src=assetsclientjs defer crossOrigin="anonymous" /> scripts </body> </html> ; }
To use your custom <Document>
, pass it to the Document option of your After.js render function.
// ./src/server.js;;;; const assets = ; const server = ;server ; ;
Call ensureReady after Window Load
if we call ensureReady on browser (client), it will try to download styles and scripts for second time, (i don't know why browsers allow this). to fix this just call ensureReady on windows.onload method:
window { }
Typescript Support
This Project Written in TypeScript and types available in index.d.ts
so there is no need to install any @types/
package.
Contribution
Feel free to propose changes or open issues.
I'm really thinking about duplicate values in routes (name and webpackChunkName) but couldn't find any suitable solution.
we can use webpackChunkName with dynamic import to name chunks by requested filename.
// routes.js { return } const routes = name: "About" // About.js file which located in ./pages/About.js path: "/about" name: "Home" // Home.js file which located in ./pages/Home.js path: "/" exact: true
This project was bootstrapped with TSDX.