How to do server side rendering with Feathers.js and Next.js

Feathers.js is the most impressive back-end framework that I’ve seen in the node.js world. Like Django and Django rest framework in the python world. Similar API to express.js. And very productive as you can generate rest CRUD API in seconds while still highly customizable. And has a unique concept called service and hook which distinguish it from the others. Next.js is a library that you could use to do the react server-side rendering easily along with all the toolings. Previously I set up a project which uses these 2 to do SSR, I thought it should be very easy. But to my surprise, it took me a while. I think it worths to write a blog here to share what I learned. We will use feathers-cli to generate a fresh new codebase and start from there.

1. Overall idea

Instead of feathers.js which sits in front of your front end app. Now it’s the next.js‘s responsibility to handle all the related requests. So you need to:

  1. set it up in the feathers middleware, which just like the express middleware.
  2. And you need to filter the feathers request and pass it back to feathers, otherwise, your feathers service request will be hijacked by next.js.
  3. Modify some setup generated by feathers generator if you use it. Like static folder.

2. Set up environment and next.js

2.1 Generate a feathers app

  1. Install the generator if you haven’t npm install @feathersjs/cli -g. It’s like the Cli for ruby on rails and sails.js which focuses on generating boilerplate code and project structure for you.
  2. Create the project folder: mkdir my-app.
  3. Go to the folder: cd my-app
  4. Then use feathers generator my-app to create your app.
    • When it asks for the source folder name, instead of the default src, I recommend using server, because we will have some conventions here which will make things more clear.

2.2 Add the next.js support

  1. Install dependencies: npm install next react react-dom
  2. Create folder for client-side code: mkdir client
  3. Create the pages sub folder inside client: mkdir client/pages

Why we want to have different folders?

  • Well, first, it’s clear, so all back-end code in server and front-end code in client.
  • Later on, you can have an extra ESLint settings in your client sub-folder. While ESLint could lint your node and front-end code, their rules are different, at least I prefer this way.

2.3 Create the index page

This file should be at ./client/pages/index.js.

1
export default () => <div>Welcome to next.js!</div>

Later on, when you want to access /index, next.js will render /pages/index.js. Just that easy.

2.4 First run

  1. Add a new NPM script in package.json:
    • "dev": "next ./client"
  2. Run npm run dev, and you should able to see your index.js at http://localhost:3000/index

3. Setup next.js middleware in feathers

3.1 Setup next.js base

In the my-app/server folder, create a new file named nextApp.js with the following code:

1
2
3
4
5
6
7
8
9
10
const next = require('next');
const dev = process.env.NODE_ENV !== 'production';
const nextApp = next({dir: './client', dev});
const handle = nextApp.getRequestHandler();
module.exports = {
nextApp,
handle
};

3.2 Start next.js when starting the server

Open index.js, update its code to the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const logger = require('winston');
const app = require('./app');
const port = app.get('port');
const nextApp = require('./nextApp').nextApp;
nextApp.prepare().then(() => {
const server = app.listen(port);
process.on('unhandledRejection', (reason, p) =>
logger.error('Unhandled Rejection at: Promise ', p, reason)
);
server.on('listening', () =>
logger.info('Feathers application started on http://%s:%d', app.get('host'), port)
);
});

We start the nextApp first then start the feathers server. The main benefit of this is that, later on, you could enjoy the hot module reloading while developing without any problem.

3.3 Add the next.js middleware

  1. In the my-app folder. Add a new feathers middleware: feathers generate middleware:
    • Its name is next
    • The mount path is the default: ‘‘ which means it will handle *ALL request.
  2. Open /server/middleware/index.js, update the code to this:
1
2
3
4
5
const next = require('./next');
module.exports = function (app) {
app.get('*', next());
};

The only line we changed is from app.use to app.get, still handle all requests, but only concern about the GET request now.

3.4 Handle the request in the new middleware

Update the file /server/middleware/next.js with the code:

1
2
3
4
5
6
7
const handle = require('../nextApp').handle;
module.exports = function (options = {}) {
return function next(req, res, next) {
return handle(req,res);
};
};

3.5 Try it

  1. npm run start, this time, we start the feathers server rather the next.js dev server.
  2. Open http://localhost:3030/index

4. Enhance the logic

Just like the typical problem in SPA, you still have the routing problem. Now as your next middleware will hijack all the GET request, it will hijack all the GET request for feathers services too. These are problems like this which we need to handle.

4.1 Remove the static folder support from feathers

In the /server/app.js, comment out or remove the following line:

1
app.use('/', express.static(app.get('public')));

As now all the static files should be handled in next.js, we don’t need it anymore.

4.2 Add a filter function in nextApp.js

1
2
3
4
5
6
7
8
const feathersServices = [
'/users'
];
const isFeathersService = (path) =>
feathersServices.some((item) =>
path.indexOf(item) > -1
);

And don’t forget to export it:

1
2
3
4
5
module.exports = {
nextApp,
handle,
isFeathersService
};

So, here, in that feathersServices array, you need to add the feathers service endpoint into it every time you add a feathers service like the above example.

And we use the isFeathersService function to check whether the path belongs to a feathers service or not.

Why filter service rather than next.js route path

  • It’s because there are other requests other than the page rendering which needs next.js to handle, things like all the assets on the page: the image file, the generated js file. Filtering out feathers services will be a more convenient way.

4.3 Use the filter function in next middleware

Open the /server/middleware/next.js, update the file to the following code:

1
2
3
4
5
6
7
8
9
10
11
12
const handle = require('../nextApp').handle;
const isFeathersService = require('../nextApp').isFeathersService;
module.exports = function (options = {}) {
return function next(req, res, next) {
if (isFeathersService(req.originalUrl)){
return next();
} else {
return handle(req,res);
}
};
};

5. End

You can check the whole code in my github repo.
That’s all. Hope it helps. :)