Hello!
👋

From Express and React to Next

Refactoring My Blog Rendering Engine to Leverage Proven Solution

  • Published at 
  • 8 mins reading time

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 http://localhost/.

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 Steps

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.

Gulp and Express

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.

import express from 'express';
import next from 'next';
const app = next({ dev: __DEV__ });
const server = express();
app.prepare().then(() => {
// all previous express path declaration goes after app.prepare() instead
server.get('/', (req, res) => {});
});

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).

Path Matching and Rendering Component

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).

server.get('/', (req, res) => {
// pass through query string to our rendered page
return app.render(req, res, '/home', req.query);
});
server.get('/:slug', (req, res) => {
// merge query string with our slug to be used in page later
const params = { ...req.query, slug: req.params.slug };
return app.render(req, res, '/post', params);
});
server.get('*', (req, res) => {
const handle = app.getRequestHandler();
return handle(req, res);
});

As you've seen above, app.render accepts 4 arguments. The first 2 arguments is request and response object from http or 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 pages/home.js, and /anything will render component from pages/post.js.

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.

Data Fetching

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.

export default class PostPage extends React.Component {
// instead of returning Promise, you can use async function
// and returns the object directly
static async getInitialProps(ctx) {
// get slug from query object
const { slug } = ctx.query;
const response = await fetch('http://api.paperblog.com/post/${slug}');
const post = await response.json();
return { post };
}
render() {
// you'll have this.props.post here
return <div />;
}
}

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!

Custom Document

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 <head>.

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 Document from next/document.

import Document, { Head, Main, NextScript } from 'next/document';
export default class PaperBlogDocument extends Document {
render() {
const { props } = this.props.__NEXT_DATA__;
return (
<html>
<Head>
<title>{props.meta.title}</title>
<link rel="stylesheet" type="text/css" href="/static/blog.css" />
</Head>
<body>
<Main />
<NextScript />
</body>
</html>
);
}
}

Next will automatically resolve /static/-prefixed url by returning the contents from static/ directory. I just need to move my blog.css to 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 Custom Build Config

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.

Server Build System

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

const replaceMap = {
__DEV__: process.env.NODE_ENV !== 'production',
};
export default {
entry: 'server/index.js',
dest: 'server.build.js',
plugins: [asyncToGen(), replace(replaceMap)],
externals: ['next', 'express'],
format: 'cjs',
};

I use two plugin in my rollup config. rollup-plugin-async and 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 cjs.

Caveat

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:

const handle = app.getRequestHandler();
if (__DEV__) {
server.get('/__webpack_hmr', (req, res) => {
return handle(req, res);
});
}
// place /:slug matcher after webpack hmr url
server.get('/:slug', (req, res) => {});

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.

Categorized under

Webmentions

If you think this article is helpful