This weekend, I finally got some time to work on my blog again after so long. I've been waiting Next.js to support custom server handler for some time and they finally did in v2.
My blog frontend is originally a simple Express setup with React as the component model. I use Gulp for build system for compiling server side code using Babel and bundle CSS with Autoprefixer. I don't write much logic in the client side aside from the iframe logic, some analytics & 3rd party integration script, so I just inlined them inside a script tag. It works pretty well, but it's not enough.
I have some plans to optimize my blog frontend more. But in doing so, I need to solve universal rendering problem which my current build system can't solve. This is why I'm so happy when Zeit open sourced their JS framework: Next.js.
Next is a framework for server-rendered React app. By declaring single component, Next will automatically reuse the same component in both server render and client render.
They use a simple convention to declare the component: put js file that export a React component inside
pages/ directory. The filename will be used as path matching when server receives request. For example:
pages/index.js will be used to render
In the initial version, you have to create as many file as you need to render specific path (or use query string to pass a parameters). Starting v2 you can customize which file will render which url. This new feature allow me to use Next for the next iteration for my blog (pun not intended).
Migration to other framework is usually hard. In my case, it was easier than I thought. Partly because Next is a React-based framework and I already use React for my server-rendered blog, and also because Next documentation is quite good and they have a ton of examples.
First thing I do is remove Gulp from build system. This is easily done by removing
gulpfile.js and remove all gulp modules from
devDependencies entirely. The next step is integrate Next into existing express app.
You might notice that I use
__DEV__ variable. It's a global variable that is available inside build system that is basically show whether I run my app on dev or prod (more on that later).
To tell Next which component is used to render which path or URL, we use express path matching. To send parameters to the rendered page, we use the 4th argument in
app.render method. We also use
app.getRequestHandler to handle any request other than to our page (example: JS and CSS asset).
As you've seen above,
app.render accepts 4 arguments. The first 2 arguments is request and response object from
express. The third parameter is used to get which filename will be used as the universal-rendered component. In my example above, accessing
/ will render component from
/anything will render component from
If you're wondering why we don't fetch post data before rendering and pass through the slug instead, it's because we want to be able to render the post page both server side and client side.
It's possible to put the post data in request / response object to render in server and use query string to fetch another post in client side but it's too complex.
Next has a great concept for data fetching. You declare a static property in your React component called
getInitialProps. It is a function that returns a Promise that resolve the props you want to have in the component.
This method will executed in both server and client side (when navigating). This is why we put the slug in the query object.
When navigating to another post page, Next can use the same component with different query object and fetch it client-side. Essentially re-rendering the same
PostPage component with different props. DX & Perf win!
Because I don't want to make enormous changes when incorporating Next into my codebase, I keep using global classname and a global CSS file instead of
styled-jsx. This means I have to specify which CSS to load in the
Fortunately, Next provides a way to override the html skeleton. We can do so by creating
pages/_document.js that export a class that extends
Next will automatically resolve
/static/-prefixed url by returning the contents from
static/ directory. I just need to move my
static/ directory to achieve similar result with
express.static method that I used before.
We can also access the same props in client side by accessing
this.props.__NEXT_DATA__.props inside render method. I use this for getting the
title props value because it's resolved after fetching post data.
Next is really flexible that it provides 2 method to extend the config. First one is when you want to extend the babel config that Next use internally. You just need to create
.babelrc file in root directory and Next will recognize that and use that instead.
I use it to replace global variables (like
__DEV__) across my codebase using
transform-define plugin. Make sure you use
next/babel preset when extending, unless you know what you're doing.
The second method is to extend the webpack config itself. I haven't used it because the existing config is enough for me.
You don't actually need to set up a build system for server-side code if you're only using Next. In my case, I use async function in my server side code which is not supported yet in Node v6.9.4 (latest LTS). My first intuition is just use
async-to-gen module which is a small module to convert async function to generator which is available in Node 6.
But I also need to replace global variables like
__DEV__ in server side code (that is not compiled by Next). Coincidentally I see a usage in build system section in
async-to-gen documentation. It's a Rollup plugin.
I never used Rollup before, so it's a chance to learn Rollup. Configuring rollup is easy and straightforward. Create a config file (similar to webpack) that specify an entry point and output file. Here is my rollup config
I use two plugin in my rollup config.
rollup-plugin-replace. The first plugin is used to replace async function to generator.
The second plugin is used to replace variable values, similar to
babel-plugin-transform-define. It accepts a
replaceMap which is an object with key describing what variable you want to replace, and the value to replace.
I use the similar
replaceMap with the babel one with only minor difference: I need to wrap string inside
JSON.stringify call to use string value in Rollup replace plugin. Babel plugin one doesn't need this.
Finally the format property is used to specify which format the output will be. Because I used it for server side code, I use
Next uses webpack hot module replacement so it will connect to
/__webpack_hmr to update the module when developing locally. Because my blog urls use the root path and the
/:slug will match all paths except asset, I have to create another path to handle request to webpack dev server:
Basically that's it.
I learn a lot when migrating my blog to use Next. I'm really happy that these tools exists so I can build something more easily. I don't have to use what I don't need. I can still use inline script (like I still do) or experimenting with some framework or build system. The choice is up to me.
Well, unless when I have to work for someone.