Next.js and Typescript starter project

I’m a big fan of reusing and improving code and techniques from past projects, and using this reusable code as the building blocks of future projects. I created a React Native and JWT boilerplate project a while back which I’ve since had positive feedback from, so I wanted to make something using a different technology stack. Specifically Next JS for React server side rendering (SSR), written in Typescript and using JSON Web Tokens for authentication.

This post will go through the pain points and gotcha’s I faced while creating this starter project, why certain decisions were made, and giving reasons why I think it’s a great starter repo for a small to medium sized project.

I’ll be showing some small code snippets in this post, but they will be from the project at the time of writing. They are likely to change over time, so please reference the repo for the most recent info. Links at the bottom of the post.

Here’s the link to the repo:

https://github.com/jaygould/nextjs-typescript-jwt-boilerplate/tree/master/api

Intro - tech stack overview

For those who know all about the tech stack mentioned above, feel free to read onto the next section. If not, here’s a quick overview of why this stack is so awesome.

Next.js

The foundation of this set up is Next.js. Next is a powerful React framework which takes away some of the (few) disadvantages which come with using standalone React. Next has many features such as being compatible with Electron and generating static sites, but this starter project uses the server side rendering (SSR) aspect.

React is usually sent down to the browser as one whole JS bundle and compiled to run in the browser (assuming there’s no manual code-splitting). This can lead to issues with general performance if the app is large and contains many dependencies, and also means the website can take a few seconds to compile to something viewable by the user. Next.js helps solve these pain points by rendering the React code on the server and sending optimised, bundled code down to the user, taking some of the work away from the browser. Next also offers automatic code-splitting out of the box, so only code required for a given page is rendered on the server and sent down the wire.

Normal client side rendered React apps can overcome these problems, and can also be server side rendered without the use of Next.js, but Next simply helps get a project up and running much quicker and easier.

Next is useful for so many more reasons though, most of which I won’t cover in this post, but it’s worth going to their site if you want to see more.

Typescript

I have recently written a post about Typescript and its uses within React, so I won’t go into too much detail here, but Typescript is used in this project on both the client side (Next) and the server side API (Node). It is helpful in both of these settings to ensure we are getting great intellisense whilst using your IDE, and providing a nice type system to ensure type errors are caught at dev time rather than in production.

JWT authentication

JSON Web Tokens (JWT’s) are a staple for modern web applications nowadays. They are used so often because they are “stateless”, meaning they can be verified on your server without looking up sessions in a database or any other session storage system. They can contain information relating to a user (such as user ID, permissions etc.) and also be set to expire after a certain time.

PostgreSQL with Sequelize

Postgres is a fantastic database solution which lends itself to the relational aspects of MySQL. It’s robust, widely used and documented, and great for all sized projects. I tend to stay away from MongoDB lately unless it’s a really small playground type project, but that’s just because Postgres is so easy to set up once you have a good base project (like this one), easy to use, and comes with so many features. That’s not to say I don’t like MongoDB because I do, but I just prefer Postgres.

Having said that, I rarely use Postgres on its own. Instead I go for Sequelize, which is a ORM database management tool built for Node.js. Essentially this takes the complexity away from writing raw Postgres queries, and allows you to write queries in an object based way (very similar to MongoDB). Sequelize is an excellent solution for small to medium sized projects, and makes it simple to work with Postgres (and in fact any other DB system such as MySQL, MariaDB and MSSQL).

Project setup

Before I explain about each of the sections in more detail, I’m going to cover the top level structure for the starter project. The project is contained within one repo, but has two completely separate sections - the Next.js client side application, and the Node.js server side API.

  • The Next.js application is server side rendered and therefore contains a Node/Express installation to process the SSR. There are no direct database connections here as it’s only used to render the front-end of the application.
  • The data API is used to connect to the database and send/receive data for authentication and all other data needs. This is built in Node too, but a separate installation than that of the Next.js front-end application.

This separation is considered a best practice when working with Next. The core maintainer even said this on a recent Shop Talk Show podcast. In fact, it’s no different than having a separate normal React project and associated API. It helps keep files separated and organised way, while providing the separation of concerns that modern applications benefit from.

I did originally have these in 2 separate repositories, but I have recently merged them after much thought due to how much easier it is to get up and running that way. In production though, I would personally separate them out. I’ve explained more about that in the readme.

Setting up Next.js

Next has great documentation, but there are a few things I did which deviated from the “out of the box” solution. Firstly, it’s worth noting that Next runs and renders all pages on the server by default as I mentioned earlier. It does this using Node.js, but you don’t need to mess with any Node when using the out of the box Next installation. You can just run next start and everything is done for you. Although it’s easy, this way means you can’t customise anything else on the server - the main part being that you can’t create custom routes such as website.com/user/[your-user-name]. In order to do this, you need to run Next from a custom server.

As I said, the docs are great so they offer you examples of how to get a custom server running. My project runs Next.js from a Node server with Express installed, offering an easy and familiar setting (to me at least) to handle things like custom routes.

Aside from the custom Node server, I also opted to use Typescript for the Next app. Using Typescript in this setting means the custom Node index file (in my case, server.ts), alongside all service and component files can be written in Typescript and automatically compiled into Next’s .next directory which contains the compilation output.

Again, Next provides a nice and easy way to integrate Typescript. Typescript can often require configuration of Webpack or Babel to run alongside, but Next provides a way to extend the base Webpack config using pre-made modules, including a module for Typescript.

Using good old function programming concepts, we can link together these modules. I used pre-made modules for Typescript, Sass, and custom Babel config:

module.exports = withCustomBabelConfigFile(
  withTypeScript(withSass({
    cssModules: true,
    babelConfigFile: path.resolve("./babel.config.js")
  }))
);

This meant I can specify that I’d like to use CSS modules, a custom Babel config file, and offers many other options too.

The babel.config.js file uses the Next preset for Babel, as well as the Babel Typescript preset, allowing further customisation for other Babel plugins as the project grows:

module.exports = function(api) {
  api.cache(true);

  const presets = [
    "next/babel",
    ["@babel/preset-typescript", { isTSX: true, allExtensions: true }]
  ];

  const plugins = [];

  return {
    presets,
    plugins
  };
};

Another custom addition to Next which I feel is worth mentioning is the usage of Jest. Jest doesn’t need to be integrated in to Next processes. Instead, it can be used to test component and service files as in normal React. It’s relatively easy to set up Jest anyway, but here’s the jest.config.js file in my starter:

module.exports = {
  clearMocks: true,
  coverageDirectory: "coverage",
  // set up for enzyme and allowing scss
  setupFilesAfterEnv: ["<rootDir>/ui/tests/_setup.tests.ts"],
  moduleNameMapper: {
    "\\.(css|less|scss)$": "identity-obj-proxy"
  }
};

The setupFilesAfterEnv references a setup file which is required in order to use Enzyme alongside React 16 (for testing the rendering of React components). This is very useful and only a simple addition to the config file. There were so many examples of different ways to integrate Jest with Next and Typescript when I was looking initially, but in the end it only needs this one line pretty much. The moduleNameMapper is needed in order to process files which import styling. Generally styles aren’t needed when testing with React as we are only testing input/output and data. Styles complicate the testing complication with Jest, so this option bypasses this issue.

Aside from the setup and integration of tools, another interesting part of my starter project is the authentication. I’ll try and cover this as simple as I can, but it will go in to some core concepts of Next SSR so if you are looking for detailed info about that, I’d suggest giving it a quick Google and come back.

In a normal React app (non-SSR), protected routes like a logged-in pages are hard to keep away from the user because the whole application is sent down to the user. It can be hidden from them, but ultimately the page (without data hopefully) can still be accessed by digging around in the JS bundle. On the other hand as Next.js is server side rendered, we can protect routes from the server level and stop any unwanted info being sent down to the user. There are a few ways to handle this, but I opted to do it using a Next.js only component lifecycle method called getInitialProps().

This getInitialProps() method is called both on the server and on the client, and is used to get data on the server before the page is sent to the user, and also to rehydrate the client once it mounts on the front end. This is great for authentication as we can check the user’s JWT on the server side (once it’s saved in a cookie), and decide whether to send back the current page or redirect the user to a different page depending on the validity of the token. In psuedo code this can look something like:

get auth token from the cookie in server request header (if exists)

if no auth token exists, send the user back to the login screen

if auth token does exist, send a request off to our API to check it's validity

if valid, allow user to visit the page, else log the user out

This may seem a little weird as we’re sending a fetch request from one server-side (Next server) to another server-side (our Node API). This is possible because of a great package called Isomorphic Unfetch. It changes its implementation depending on if it’s running on a client or server side setting, meaning the same package can be used to send fetch request on the Next server and client. Incredible.

Be sure to check out the starter files though as there’s a bit more detail covered there.

Setting up out Node.js API

Moving onto the Node.js API, this is a little more familiar to most people who have perhaps not used Next before. I have gone for Express as the Node framework as I’m able to reuse a previous Node setup from another project. Like I mentioned at the start of this post, it’s great to reuse things whenever possible! Unlike the Next.js project, this server required more setup as there was no pre-configured Webpack and Typescript.

Again I have gone for Typescript which is probably more useful on the server side than the React/Next front end in my opinion. For development, I’m not compiling the Typescript down to JS output, but instead using the ts-node-dev package to compile straight to the Node process, allowing for really fast in-time compilation. My package.json script for the development is "dev": "ts-node-dev --respawn --transpileOnly ./src/server.ts".

Babel and Webpack can be used to compile Typescript to production-ready JS, but Typescript also comes with its own way of compiling down to JS. The aim of this project is to keep everything as simple as possible and not add any unnecessary dependencies, so I decided to have Typescript compile itself using "prod": "tsc && NODE_ENV=production node ./build/server.js". The tsconfig.json file then needs to reflect the project options and input/output folders for compilation, but all this can be seen on the repo.

The only other tool integrated in to the API is Jest again. Similar to the client side, Jest is easily integrated to Typescript, requiring very little configuration. The only notable addition from a bare Jest installation is the following property to the jest.config.js file: preset: 'ts-jest'. This ts-jest package is a preprocessor for Typescript, allowing Jest to focus on testing the processed code on demand.

The folder structure for the API is quite standard as far as server side architecture goes. A simple API directory with files to handle API endpoints, services to process the data from API endpoints, and also models to handle the actual database queries. Each of these directories have a clear and obvious split so we can easily see what should be handled in what file. For example, if we have a query to process it must go in a model, and if we need to go some data massaging, it must go in a service file. This separation is essential for keeping maintainable code as a project grows.

Aside from the tools for the API, I wanted to quickly cover the database. Most starter projects out there are opinionated when it comes to database storage, but I have chosen to add a database solution in my repo to help getting up and running easier. This will hopefully help things run quicker as all database models have already been made, and if you do want to swap out for something other than Postgres, you can by providing different functions in place of the current models. As the service files are so decoupled from the models, only one file will need changing in order to swap out for another database.

Thanks for reading

I’ve added installation instructions on the Github repo which explain how to do things like add test data, update the environment variables and setting up the database, so check it out and let me know if you need a hand getting anything set up or explaining. Also feel free to fork or contribute!