cra-to-next

Migrate a React application created with CRA to Next.js

Example

# This plan was created using the guide from https://nextjs.org/docs/app/building-your-application/upgrading/from-create-react-app

constants:
  cleanup_files:
    - src/index.tsx
    - src/index.ts
    - src/index.jsx
    - src/index.js
    - public/index.html
    - src/reportWebVitals

setup:
  # Make sure node_modules is in .gitignore or else all of the node_modules dependencies will get added to the commit
  - name: edit_file
    arguments:
      path_to_file: .gitignore
      edit_prompt: |
        this is a .gitignore file.  Please add node_modules to the list
  - name: run_terminal_command
    arguments:
      command: npm install next@latest
  - name: run_terminal_command
    arguments:
      command: npm install postcss autoprefixer

steps:
  # Step 2: create Next.js configuration file
  - name: Create Next.js configuration file
    tools:
      - name: create_file
        arguments:
          path_to_file: next.config.mjs
          file_contents: |
            /** @type {import('next').NextConfig} */
            const nextConfig = {
              output: 'export', // Outputs a Single-Page Application (SPA).
              distDir: './dist', // Changes the build output directory to `./dist/`.
            }

            export default nextConfig
  # Step 3: update the tsconfig.json file
  - name: Update the tsconfig.json file
    tools:
      - name: edit_file
        arguments:
          path_to_file: tsconfig.json
          edit_prompt: |
            This is a tsconfig.json file.  Update it with the following configurations:

            {
              "compilerOptions": {
                "target": "es5",
                "lib": ["dom", "dom.iterable", "esnext"],
                "allowJs": true,
                "skipLibCheck": true,
                "strict": false,
                "forceConsistentCasingInFileNames": true,
                "noEmit": true,
                "esModuleInterop": true,
                "module": "esnext",
                "moduleResolution": "node",
                "resolveJsonModule": true,
                "isolatedModules": true,
                "jsx": "preserve",
                "baseUrl": ".",
                "incremental": true,
                "plugins": [
                  {
                    "name": "next"
                  }
                ],
                "strictNullChecks": true
              },
              "include": [
                "next-env.d.ts",
                "**/*.ts",
                "**/*.tsx",
                ".next/types/**/*.ts",
                "./dist/types/**/*.ts"
              ],
              "exclude": ["node_modules"]
            }
  # Steps 4 and 5: create root layout
  - name: Create root layout
    tools:
      - name: find_files_by_name_with_regex
        arguments:
          find_file_name_pattern: '.css$'
          path_to_directory: src
        returns: css_files
      - name: find_content_in_file_with_ai
        arguments:
          path_to_file: public/index.html
          find_context_prompt: |
            find the title and description meta data and return the values in the following format:

            {
              "title": <title>
              "description": <description>
            }
        returns: index_meta_data
      - name: create_file_with_ai
        arguments:
          path_to_file: app/layout.tsx
          create_content_prompt: |
            start with the following Next root layout:

            ```
            import type { Metadata } from 'next'

            export const metadata: Metadata = {
              title: 'React App',
              description: 'Web site created with Next.js.',
            }

            export default function RootLayout({
              children,
            }: {
              children: React.ReactNode
            }) {
              return (
                <html lang="en">
                  <body>
                    <div id="root">{children}</div>
                  </body>
                </html>
              )
            }
            ```

            Add imports for the following CSS files:

            {{ css_files }}

            Update the Metadata object with the following information:

            {{ index_meta_data }}
  #Step 6: create postcss.config.js file
  - name: Create postcss.config.js file
    tools:
      - name: create_file
        arguments:
          path_to_file: postcss.config.js
          file_contents: |
            module.exports = {
              plugins: {
                tailwindcss: {},
                autoprefixer: {},
              },
            }
  #Step 7: create the entry point files
  - name: Create the entry point files
    tools:
      # create server side entry point file
      - name: create_file
        arguments:
          path_to_file: app/[[...slug]]/page.tsx
          file_contents: |
            import '../../index.css'
            import { ClientOnly } from './client'

            export function generateStaticParams() {
              return [{ slug: [''] }]
            }

            export default function Page() {
              return <ClientOnly />
            }
      # create client side entry point file
      - name: create_file
        arguments:
          path_to_file: app/[[...slug]]/client.tsx
          file_contents: |
            'use client'

            import React from 'react'
            import dynamic from 'next/dynamic'

            const App = dynamic(() => import('../../App'), { ssr: false })

            export function ClientOnly() {
              return <App />
            }
  # Step 8: update static image imports
  - name: Update static image imports
    tools:
      - name: find_files_by_name_with_regex
        arguments:
          find_file_name_pattern: '.(tsx|ts|jsx|js)$'
        returns: react_files
      - name: async_each
        items: '{{ react_files }}'
        each_item:
          item_name: react_file
          tools:
            - name: edit_file
              arguments:
                path_to_file: '{{ react_file }}'
                edit_prompt: |
                  If this is a JSX or TSX React file, then make the following changes.  Otherwise, do NOT make any changes to this file.

                  If you see any image imports like the following

                  ```
                  import logo from '/logo.png'
                  ```

                  Please update the import path to be a relative path given that this file path is `{{ react_file }}` and the path to the public directory is `/public`

                  ```
                  import logo from '../public/logo.png'
                  ```

                  If you see any image tags that use the image object from an import like this:

                  ```
                  <img src={logo} />
                  ```

                  Please update the src to use the image object "src" property like this:

                  ```
                  <img src={logo.src} />
                  ```

  # Step 9: update environment variables
  - name: Update environment variables
    tools:
      - name: find_files_by_name_with_regex
        arguments:
          find_file_name_pattern: '.env$'
        returns: env_files
      - name: async_each
        items: '{{ env_files }}'
        each_item:
          item_name: env_file
          tools:
            - name: edit_file
              arguments:
                path_to_file: '{{ env_file }}'
                edit_prompt: |
                  Change all environment variables with the REACT_APP_ prefix to NEXT_PUBLIC_

  # Step 10: update scripts in package.json
  - name: Update scripts in package.json
    tools:
      - name: edit_file
        arguments:
          path_to_file: package.json
          edit_prompt: |
            this is a package.json file for a CRA application.  We are migrating the application to Next.js.  Please add the following scripts:

            ```
            {
              "scripts": {
                "dev": "next dev",
                "build": "next build",
                "start": "next start"
              }
            }
            ```
      - name: edit_file
        arguments:
          path_to_file: .gitignore
          edit_prompt: |
            this is a .gitignore file for a CRA application.  We are migrating the application to Next.js.  Please add the following ignores:

            ```
            # ...
            .next
            next-env.d.ts
            dist
            ```

  # Step 11: cleanup
  - name: Cleanup
    tools:
      # remove CRA specific files
      - name: async_each
        items: '{{ cleanup_files }}'
        each_item:
          item_name: cleanup_file
          tools:
            - name: delete_file_or_folder
              arguments:
                path_to_target: '{{ cleanup_file }}'
      # remove CRA specific dependencies
      - name: run_terminal_command
        arguments:
          command: npm uninstall react-scripts

Last updated