Next.js is a frontend framework built on the top of React. It has many features that allow to fine-tune and optimize web applications and simplify their development, including Client-Side (CSR) and Server-side Rendering (SSR) and improve development experience with fast hot reloading.
At Ensolvers we maintain and continue working in traditional React applications that have been in production for several years and conceived with more classical setups like Create React App. However, some time ago we decided to start migrating all these applications to Next.js to start using its features.
As aforementioned, Next.js offers various interesting features in comparison to using a raw React setup. However, migrating an existing client-side app without deep changes involves a sequence of steps. The first phase we describe in this document is to migrate the apps to a Next.js setup using 100% Static Site Generation (SSG), so we have (1) Next.js available to start making use of their features and (2) use their mature bundler/compiler and dev environments.
The first step is to create an empty Next.js app
At this point, Next.js requests a set of configuration parameters. In our case, we just keep those that will match the current project configuration - preserving TypeScript and maintaining the /src folder.
In our case, to preserve the same behaviour that is present in SPAs, we wanted to maintain React Router as the main router framework. This was needed since migrating to Next.js Router would have implied a lot of changes in the structure of the code and navigation framework. The only side-effect we've found when doing that is that in the dev environment, since Next.js runs its own server, routes on the "client" React app (i.e., React Router Routes) return a 404 since, effectively, those pages don't exist in the Next.js navigation realm but are correctly interpreted and processed on the client-side. To solve this issue we needed to define a middleware.ts file in our project root folder with the code below, which basically does an internal rewriting of all requests so the content of the index (/) page of the repo is returned in all cases (except for some cases which are defined as exceptions). As a result, the index page will load the React client-side app and then the client-side Router will take care of rendering the right screen.
Next step is to start moving all our previous code to the `src/` folder. In this case, we have to consider an important fact: by default, Next.js tries to apply server-side rendering. However, in our app, since we were based on a traditional React setup (which is designed to run on the client-side by default) there might be some unsupported behaviour in the components of the app. So, we need to add the `use client` directive to individual components and on the root level too.
Then the next step is to run the application in dev and check if everything looks good
After that, we need to ensure that we are generating a bundle that can run without a server and just run in a CDN as a SPA. Thus, we need to add the following to the next.config.js file
Since our previous CI/CD pipeline config generated all the output into the `dist` folder, we define the `export` script into the `package.json` file as following
And finally, we tweak the building commands to be as following to generate the static site
In this article we explained a simple, step-by-step guide to migrate a classic SPA written in React to Next.js. Despite the advantages of doing that are limited due to CSR (and SSG), performing this migration opens the door to start using features that Next.js provides in an iterative way in already migrated projects.