Migrate a non-CRA React project to Next.js

Estee Tey's photo
Estee Tey
ยทSep 26, 2021ยท

8 min read

Migrate a non-CRA React project to Next.js

Subscribe to my newsletter and never miss my upcoming articles

Listen to this article

In this tutorial, I will be demonstrating how I migrated a React project to Next.js. This project is not created by Create React App (CRA), to simulate most apps that are ejected.

The following will be absent to make the guide simpler to understand:

  • Routes
  • Environment Variables
  • Search Engine Optimization (SEO)

Optional: If you're keen to look at full changelog at a glance before we start, you can refer to this commit in the github repository.


Initial Project Structure ๐Ÿ“‚

This is the original React repository.


If you prefer navigating files in your own editor, you can clone the repository and check out the before-migrate-to-nextjs branch.

I created this app using the react-pdf webpack5 sample repository as a base and then adding my own components on top of it. I did this as part of my previous experiments for react-pdf.

So at the moment, the project looks like this:


  • you can upload a pdf and you can highlight text to get it in the input field.
  • when you click save note, it will save it in memory and show it below the input field.

To migrate to Next.js, I went to their documentation to search for migration guides. As of September 2021, there are only migration guides for:

  1. create-react-app
  2. Gatsby
  3. react-router

What about non-CRA React apps? Well, not all hope is lost! ๐Ÿ’ช

The create-react-app migration guide is still very helpful to migrate my non-CRA React app. Below are the sections that were relevant for this simple non-CRA React project.

  1. Updating package.json and dependencies
  2. Static Assets and Compiled Output
  3. Styling

Let's go through these steps!

1. Updating package.json and dependencies

Initial dependencies

"dependencies": {
    "@chakra-ui/react": "^1.6.7",
    "@emotion/react": "^11.4.1",
    "@emotion/styled": "^11.3.0",
    "@popperjs/core": "^2.10.1",
    "framer-motion": "^4.1.17",
    "react": "^17.0.0",
    "react-dom": "^17.0.0",
    "react-pdf": "latest",
    "react-popper": "^2.2.5"
  "devDependencies": {
    "@babel/core": "^7.12.0",
    "@babel/preset-env": "^7.12.0",
    "@babel/preset-react": "^7.12.0",
    "babel-loader": "^8.0.0",
    "copy-webpack-plugin": "^9.0.0",
    "css-loader": "^6.0.0",
    "html-webpack-plugin": "^5.1.0",
    "style-loader": "^3.0.0",
    "webpack": "^5.20.0",
    "webpack-cli": "^4.7.0",
    "webpack-dev-server": "^4.0.0"

Initial run scripts

"scripts": {
    "build": "NODE_ENV=production webpack",
    "start": "NODE_ENV=development webpack serve"

Based on the guide, since I had no react-scripts, I just had to install Next.js

npm i next

and also replace the initial run scripts to

"scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"

To test it locally, I ran npm run dev , but I couldn't even start it yet.

ready - started server on, url: http://localhost:3000
info  - Using webpack 5. Reason: Enabled by default https://nextjs.org/docs/messages/webpack5
info  - Using external babel configuration from /home/lyqht/Github/dr-teck/.babelrc
Error: > Couldn't find a `pages` directory. Please create one under the project root
    at Object.findPagesDir (/home/lyqht/Github/dr-teck/node_modules/next/dist/lib/find-pages-dir.js:33:11)
    at new DevServer (/home/lyqht/Github/dr-teck/node_modules/next/dist/server/dev/next-dev-server.js:101:44)
    at NextServer.createServer (/home/lyqht/Github/dr-teck/node_modules/next/dist/server/next.js:104:20)
    at /home/lyqht/Github/dr-teck/node_modules/next/dist/server/next.js:119:42
    at async NextServer.prepare (/home/lyqht/Github/dr-teck/node_modules/next/dist/server/next.js:94:24)
    at async /home/lyqht/Github/dr-teck/node_modules/next/dist/cli/next-dev.js:121:

Currently this project doesn't follow Next.js's required project structure yet. Let's fix this step by step. Since the error says I don't have a pages directory, then I will make a pages directory. When I run npm run dev again, it seems to look ok , but when I try visiting localhost:3000, then there's yet another error ๐Ÿคฆโ€โ™‚๏ธ

ready - started server on, url: http://localhost:3000
info  - Using webpack 5. Reason: Enabled by default https://nextjs.org/docs/messages/webpack5
info  - Using external babel configuration from /home/lyqht/Github/dr-teck/.babelrc
event - compiled successfully # this line made it look like it was gonna work
event - build page: /next/dist/pages/_error # this happens when I visit the site
wait  - compiling...
event - compiled successfully
ReferenceError: regeneratorRuntime is not defined
    at /home/lyqht/Github/dr-teck/.next/server/pages/_document.js:687:62
    at /home/lyqht/Github/dr-teck/.next/server/pages/_document.js:729:6
    at Object../node_modules/next/dist/pages/_document.js (/home/lyqht/Github/dr-teck/.next/server/pages/_document.js:733:2)
    at __webpack_require__ (/home/lyqht/Github/dr-teck/.next/server/webpack-runtime.js:25:42)
    at __webpack_exec__ (/home/lyqht/Github/dr-teck/.next/server/pages/_document.js:1365:39)
    at /home/lyqht/Github/dr-teck/.next/server/pages/_document.js:1366:28
    at Object.<anonymous> (/home/lyqht/Github/dr-teck/.next/server/pages/_document.js:1369:3)us> (/home/lyqht/Github/dr-teck/.next/server/pages/_document.js:1369:3)

Well, in this error, even though it is not explicit what is causing the regeneratorRuntime to be undefined, you can see the file where the exception is called in .next/server/pages/_document.js , a file within our project repository itself. This new .next build folder is generated by Next.js even when we start the app locally.


Upon reading further in the guide, I noticed that the _app.js and _document.js in the .next build folder are actually mentioned in the next section for Static Assets and Compiled Output.

2. Static Assets and Compiled Output

According to this section, in the /pages folder, there are specific files that Next.js looks for when they try to start the app. So these are the steps that I took.

  1. Moved my sample pdfs into /public
  2. Moved my /components folder into this folder
  3. Moved the entry point file index.jsx into this folder, and changed it to _app.js
  4. Moved the public html document index.html into this folder and changed it to index.js

Initial index.jsx

import { ChakraProvider } from "@chakra-ui/react";
import React from "react";
import { render } from "react-dom";
import PDFViewer from "./components/PDFViewer";

    <PDFViewer />

After changing to _app.js

import { ChakraProvider } from "@chakra-ui/react";
import React from "react";
import PDFViewer from "./components/PDFViewer";
import "react-pdf/dist/esm/Page/AnnotationLayer.css";

function MyApp({ Component, pageProps }) {
  return (
      <PDFViewer />

export default MyApp

Initial index.html

<!DOCTYPE html>
<html lang="en-US">
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <div id="react-root"></div>

After changing to index.js

import Head from "next/head";
import Image from "next/image";

export default function Home() {
  return (
        <meta name="description" content="Generated by create next app" />
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <link rel="icon" href="/favicon.ico" />

        <div id="react-root"></div>

And with all the changes above, we retry starting up the app locally with npm run dev. This time, we still have an error, but it looks a lot more simpler to fix! โœจ We are getting somewhere!

ready - started server on, url: http://localhost:3000
info  - Using webpack 5. Reason: Enabled by default https://nextjs.org/docs/messages/webpack5
info  - Using external babel configuration from /home/lyqht/Github/dr-teck/.babelrc
(node:4318) [DEP_WEBPACK_MODULE_ISSUER] DeprecationWarning: Module.issuer: Use new ModuleGraph API
error - ./pages/components/Navbar.css
Global CSS cannot be imported from files other than your Custom <App>. Due to the Global nature of stylesheets, and to avoid conflicts, Please move all first-party global CSS imports to pages/_app.js. Or convert the import to Component-Level CSS (CSS Modules).
Read more: https://nextjs.org/docs/messages/css-global
Location: pages/components/Navbar.jsx

This error brings us to the next section on Styling.

3. Styling

In my initial components, say Navbar.jsx, if there is a custom stylesheet for that component, I would name it Navbar.css and import it as such

import "./Navbar.css";

const NavBar = ({someProp}) => {
     return (<div className={"sticky"}> ... </div>)

In Next.js, they need such component-specific stylesheets to be CSS Modules. Luckily, it is quite easy to convert the files into modules! We just have to do the following:

  1. *.css โ†’ *.module.css
  2. Change the way that we import the styles

After changing the way we import the style, the above example becomes

import styles from "./Navbar.module.css";

const NavBar = ({someProp}) => {
     return (<div className={styles.sticky}> ... </div>)

If I wasn't using react-pdf in my project, that would have been the end of the process of migrating from React to Next.js ๐Ÿคฏ.

Unfortunately there are certain packages that work a little differently, and the usual way of using them is not supported in the context of Next.js ๐Ÿ˜ข. This will be elaborated in the next section.

Possible hiccup(s) you may face in migrating

This was how I have been using and importing components from react-pdf before migrating to Next.js - it is a service worker kind of implementation as recommended by the README.md of the react-pdf repository.

import { Document, Page } from "react-pdf/dist/esm/entry.webpack";

Now, I'm encountering the following error.

import * as pdfjs from 'pdfjs-dist'; // eslint-disable-next-line

SyntaxError: Cannot use import statement outside a module

So I thought hmm, okay maybe let's try going without the service worker implementation so I'm gonna just import it like normal libraries.

import { Document, Page } from "react-pdf";

Now, there's no more errors, and I'm finally able to visit my site! But happiness is short-lived, and my initial PDF was not able to load. There's also an error in the Console ๐Ÿ˜•


Error: Setting up fake worker failed: "Cannot load script at: http://localhost:3000/pdf.worker.js".

According to this Github issue, React PDF 4.x does not work without a service worker ๐Ÿ˜…. Thankfully, the Open source community is awesome and there were commenters that gave a working fix which involve setting the pdf.js service worker to a CDN version directly.

import { Document, Page, pdfjs } from "react-pdf";
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;

๐Ÿ’ก So, a takeaway here is that sometimes if you're trying to migrate a project to a new framework, be careful of such possible issues, and try to look them on the original Github repository for those offending packages.


This the final folder structure.


My site now looks and works the same as before migrating ๐Ÿ˜‡


The full changelog can once again be found here.

And we're done! ๐ŸŽ‰

Bonus: GitHub security vulnerability check

After pushing this production-ready commit to the main branch, Github also raised a security vulnerability alert to me, which might be due to the migration to Next.js. It's really cool that they even have a feature for the users to choose to apply an automated to fix the security vulnerability.


That's a wrap folks! ๐ŸŽ‰

birds excited

Thank you for reading, hope you enjoyed the article!

If you find the article awesome, hit the reactions ๐Ÿงก and share it ๐Ÿฆ~

To stay updated whenever I post new stuff, follow me on Twitter.

Did you find this article valuable?

Support Estee Tey by becoming a sponsor. Any amount is appreciated!

See recent sponsors |ย Learn more about Hashnode Sponsors
Share this