next-12-to-13

Upgrade your Next.js 12 project to Next.js 13

Example

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

constants:
  cleanup_files:
    - pages/_app.js
    - pages/_document.js

setup:
  - name: get_content_from_file
    arguments:
      path_to_file: .gitignore
    returns: gitignore_contents
  - name: if_else
    condition: '{{ gitignore_contents }}'
    else:
      tools:
        - name: create_file
          arguments:
            path_to_file: .gitignore
            file_contents: |
              node_modules
              .pnp
              .pnp.js

              # Testing
              coverage

              # Production
              build
              dist

              # Environment variables
              .env
              .env.*
              !.env.example

              # Logs
              npm-debug.log*
              yarn-debug.log*
              yarn-error.log*
              pnpm-debug.log*
              lerna-debug.log*

              # Editor directories and files
              .DS_Store
              .vscode
              .idea
              *.swp
              *.swo

  - name: run_terminal_command
    arguments:
      command: npm install next@latest react@latest react-dom@latest
  - name: run_terminal_command
    arguments:
      command: npm install -D eslint-config-next@latest

steps:
  # Step 1: Ensure Node.js version is >= v18.17
  - name: Check Node.js version
    tools:
      - name: run_terminal_command
        arguments:
          command: node -v
        returns: node_version
      # - name: assert
      #   arguments:
      #     condition: "{{ node_version | version_compare('>= 18.17') }}"
      #     message: "Node.js version must be >= v18.17"

  # Step 2: Create the app directory
  - name: Create the app directory
    tools:
      - name: create_directory
        arguments:
          path_to_directory: app

  # Step 3: Create a Root Layout
  - name: Create Root Layout
    tools:
      - name: create_file
        arguments:
          path_to_file: app/layout.tsx
          file_contents: |
            import { Metadata } from 'next'

            export const metadata: Metadata = {
              title: 'Home',
              description: 'Welcome to Next.js',
            }

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

  # Step 4: Migrate next/head to Metadata API
  - name: Migrate next/head to Metadata API
    tools:
      - name: find_files_by_name_with_regex
        arguments:
          find_file_name_pattern: '.tsx$'
        returns: tsx_files
      - name: async_each
        items: '{{ tsx_files }}'
        each_item:
          item_name: tsx_file
          tools:
            - name: edit_file
              arguments:
                path_to_file: '{{ tsx_file }}'
                edit_prompt: |
                  If this is a file with next/head usage, replace it with Metadata API as shown below:

                  Before:
                  import Head from 'next/head'
                  export default function Page() {
                    return (
                      <>
                        <Head>
                          <title>My page title</title>
                        </Head>
                      </>
                    )
                  }

                  After:
                  import { Metadata } from 'next'
                  export const metadata: Metadata = {
                    title: 'My Page Title',
                  }
                  export default function Page() {
                    return '...'
                  }

  # Step 5: Migrate pages to app directory
  - name: Migrate pages to app directory
    tools:
      - name: find_files_by_name_with_regex
        arguments:
          find_file_name_pattern: '.tsx$'
          path_to_directory: pages
        returns: page_files
      - name: async_each
        items: '{{ page_files }}'
        each_item:
          item_name: page_file
          tools:
            - name: create_file
              arguments:
                path_to_file: "app/{{ page_file | replace('pages/', '') }}"
                file_contents: |
                  import { Metadata } from 'next'
                  export const metadata: Metadata = {
                    title: 'Page',
                    description: 'Migrated page',
                  }
                  export default function Page() {
                    return '...'
                  }

  # Step 6: Migrate routing hooks
  - name: Migrate routing hooks
    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 file uses the useRouter hook from 'next/router', replace it with the new routing hooks from 'next/navigation':

                  Before:
                  import { useRouter } from 'next/router'

                  After:
                  import { useRouter, usePathname, useSearchParams } from 'next/navigation'

  # Step 7: Migrate data fetching methods
  - name: Migrate data fetching methods
    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 file uses getServerSideProps or getStaticProps, replace them with the new data fetching methods:

                  Before:
                  export async function getServerSideProps() {
                    const res = await fetch(`https://...`)
                    const projects = await res.json()
                    return { props: { projects } }
                  }

                  After:
                  async function getProjects() {
                    const res = await fetch(`https://...`, { cache: 'no-store' })
                    const projects = await res.json()
                    return projects
                  }
                  export default async function Page() {
                    const projects = await getProjects()
                    return (
                      <ul>
                        {projects.map((project) => (
                          <li key={project.id}>{project.name}</li>
                        ))}
                      </ul>
                    )
                  }

  # Step 8: Migrate 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 9: 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 Next.js 12 application. Please add the following scripts:

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

  # Step 10: Clean Up
  - name: Clean Up
    tools:
      - name: async_each
        items: '{{ cleanup_files }}'
        each_item:
          item_name: cleanup_file
          tools:
            - name: delete_file_or_folder
              arguments:
                path_to_target: '{{ cleanup_file }}'
      - name: run_terminal_command
        arguments:
          command: npm uninstall react-scripts

Last updated