Simple tutorial to migrate a non-CRA React project to Next.js
Tags: react, nextjs, javascript, todayilearned
Published: 2021-09-26 14:10:11

In this article, I will be demonstrating how I migrated a simple React project that is not bootstrapped by Create React App (CRA) to **Next.js**. 

> Optional: If you're keen to look at full changelog at a glance before we start, you can refer to this [commit](https://github.com/lyqht/dr-teck/commit/2c0cd3ff8e4d7d0c7d701715483a02c4d7b2701e?branch=2c0cd3ff8e4d7d0c7d701715483a02c4d7b2701e&diff=unified) in the github repository.

## Start

### Initial Project Structure 📂

This is the original React repository. 

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1632637226427/GTIqTocuO.png)

If you prefer navigating files in your own editor, you can clone the repository and [check out the before-migrate-to-nextjs branch](https://github.com/lyqht/dr-teck/tree/before-migrate-to-nextjs).

There are no routes, no environment variables, no search engine optimization yet to keep the guide and process simpler to understand for anyone that is new to migrating non-CRA *React* apps to *Next.js* 🧡.

This app is made using the [react-pdf webpack5 sample repository](https://github.com/wojtekmaj/react-pdf/tree/main/sample/webpack5) as a base and then adding my own components on top of it, as part of [my previous experiments for react-pdf](https://esteetey.hashnode.dev/3-easy-steps-to-work-on-your-next-side-project-more-effectively).

So at the moment, the project just looks like this:

![notes-drawer-demo.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1632665984489/_CVRnU6Vu.gif)

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

---

In order 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

But not all hope is lost! 💪

I read through the [create-react-app migration guide](https://nextjs.org/docs/migrating/from-create-react-app) and it actually helped a lot for me 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

```json
"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

```json
"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*

```bash
npm i next
```

and also replace the initial run scripts to

```json
"scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  }
```

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

```bash
ready - started server on 0.0.0.0:3000, 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:
```

*Next.js* has a specific way of structuring the project, and currently my project doesn't follow that structure yet. Let's fix this step by step. Since the error says I don't have a pages directory, then I will just make a pages directory. When I run `npm run dev` again, it seems to look ok initially, but when I try visiting localhost:3000, then there's yet another error 🤦‍♂️

```bash
ready - started server on 0.0.0.0:3000, 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.

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1632637257511/8vFAXBnw9.png)

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](https://nextjs.org/docs/migrating/from-create-react-app#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`

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

render(
  <ChakraProvider>
    <PDFViewer />
  </ChakraProvider>,
  document.getElementById("react-root")
);
```

After changing to `_app.js`

```jsx
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 (
    <ChakraProvider>
      <PDFViewer />
    </ChakraProvider>
  )
}

export default MyApp
```

Initial `index.html`

```jsx
<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Dr.Teck</title>
  </head>
  <body>
    <div id="react-root"></div>
  </body>
</html>
```

After changing to `index.js`

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

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

      <body>
        <div id="react-root"></div>
      </body>
    </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!

```jsx
ready - started server on 0.0.0.0:3000, 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](https://nextjs.org/docs/migrating/from-create-react-app#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

```jsx
import "./Navbar.css";

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

In *Next.js,* they require such component-specific stylesheets to be [CSS Modules](https://nextjs.org/docs/basic-features/built-in-css-support). 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

```jsx
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](https://github.com/wojtekmaj/react-pdf/#enable-pdfjs-worker).

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

Now, I'm encountering the following error.

```bash
/home/lyqht/Github/dr-teck/node_modules/react-pdf/dist/esm/entry.webpack.js:1
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.

```bash
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 😕

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1632637306671/COf-kc91f.png)

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

According to [this Github issue](https://www.notion.so/Migrating-a-simple-non-CRA-React-project-to-Next-js-7b068a84adf242519b65c593ba45c335), 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.

```bash
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.

---

## End

This the final folder structure.

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1632637353061/0Rq8fJfPP.png)

My site now looks and works the same as before migrating 😇

![migrate-demo.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1632637406652/WwKqJDOpH.gif)

The full changelog can once again be found [here](https://github.com/lyqht/dr-teck/commit/2c0cd3ff8e4d7d0c7d701715483a02c4d7b2701e?branch=2c0cd3ff8e4d7d0c7d701715483a02c4d7b2701e&diff=unified).

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.

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1632637421599/v99JCURbl.png)


---

Thanks for reading the article!

> **What's next:** I will be trying to integrate Notion API to actually save notes in this side project and write an article about it later on. Look out for it! ✨

If you enjoyed reading it, *react* 🧡, *feedback* 💬, *follow* me 🐧 here and [Twitter](https://mobile.twitter.com/estee_tey) !