Upgrade a Neexo Site to Next.js 16

A migration checklist for moving Neexo Next.js apps to Next.js 16, React 19, async route params, and updated caching.

AuthorNeexoCore
Updated
nextjsupgradereact

Prerequisites

  • Node.js 18.18+ (recommended: 20+)
  • Current app on Next.js 14 or 15

Migration Steps

1. Upgrade Dependencies

npm install next@latest react@latest react-dom@latest
npm install -D eslint-config-next@latest @types/react@latest @types/react-dom@latest

2. Run the Next.js Codemod

npx @next/codemod@latest upgrade

This handles many automatic transformations including async API migrations.

3. Fix Async Route APIs

params, searchParams, cookies(), and headers() are now async. Update all route components:

// Before (Next.js 14/15)
export default function Page({ params }: { params: { slug: string } }) {
  return <div>{params.slug}</div>
}

// After (Next.js 16)
export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params
  return <div>{slug}</div>
}

4. Update cookies() and headers()

// Before
const cookieStore = cookies()
const token = cookieStore.get('token')

// After
const cookieStore = await cookies()
const token = cookieStore.get('token')

5. Review Caching Changes

  • Next.js 16 no longer caches fetch() by default — add { cache: 'force-cache' } where you relied on caching
  • Use "use cache" directive for fine-grained caching instead of route segment config
  • Review generateStaticParams behavior — dynamic pages are no longer force-static by default

6. Build and Test

npm run build
npx playwright test

7. Deploy and Monitor

  • Check deployment logs after the first production deploy
  • Watch for hydration mismatches caused by client/server content differences
  • Monitor Core Web Vitals for regressions

Common Issues

  • TypeScript errors on params: ensure you're using Promise<> wrapper and await
  • Hydration mismatches: often caused by ThemeToggle or other client components that read browser state — use useSyncExternalStore for safe SSR
  • Missing data: check if you relied on default fetch caching that is now opt-in

Raw content

Copy this into your project — e.g. .instructions.md, .agent.md, or SKILL.md

## Prerequisites

- Node.js 18.18+ (recommended: 20+)
- Current app on Next.js 14 or 15

## Migration Steps

### 1. Upgrade Dependencies

```bash
npm install next@latest react@latest react-dom@latest
npm install -D eslint-config-next@latest @types/react@latest @types/react-dom@latest
```

### 2. Run the Next.js Codemod

```bash
npx @next/codemod@latest upgrade
```

This handles many automatic transformations including async API migrations.

### 3. Fix Async Route APIs

`params`, `searchParams`, `cookies()`, and `headers()` are now async. Update all route components:

```tsx
// Before (Next.js 14/15)
export default function Page({ params }: { params: { slug: string } }) {
  return <div>{params.slug}</div>
}

// After (Next.js 16)
export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params
  return <div>{slug}</div>
}
```

### 4. Update cookies() and headers()

```tsx
// Before
const cookieStore = cookies()
const token = cookieStore.get('token')

// After
const cookieStore = await cookies()
const token = cookieStore.get('token')
```

### 5. Review Caching Changes

- Next.js 16 no longer caches `fetch()` by default — add `{ cache: 'force-cache' }` where you relied on caching
- Use `"use cache"` directive for fine-grained caching instead of route segment config
- Review `generateStaticParams` behavior — dynamic pages are no longer force-static by default

### 6. Build and Test

```bash
npm run build
npx playwright test
```

### 7. Deploy and Monitor

- Check deployment logs after the first production deploy
- Watch for hydration mismatches caused by client/server content differences
- Monitor Core Web Vitals for regressions

## Common Issues

- **TypeScript errors on params**: ensure you're using `Promise<>` wrapper and `await`
- **Hydration mismatches**: often caused by `ThemeToggle` or other client components that read browser state — use `useSyncExternalStore` for safe SSR
- **Missing data**: check if you relied on default `fetch` caching that is now opt-in