---
title: "Next.js 13.3.4 Upgrade Gotchas: The Age of Server Components — Migrating from Pages Router to App Router"
description: "A complete collection of gotchas encountered when migrating from Next.js 13 Pages Router to App Router, covering Server Components, the new data-fetching model, the metadata API, and how to handle affected third-party packages."
canonical_url: "https://blog.markkulab.net/en/post/nextjs-upgrade-app-route"
author: "Mark Ku"
author_url: "https://blog.markkulab.net/en/author/mark-ku"
site: "Mark Ku's Tech Notes"
date_published: "2023-05-28 01:01:01 +0800"
category: "Frontend"
tags: ["nextjs", "react", "upgrade", "app route", "server component", "typescript", "frontend"]
language: "en"
license: "CC BY 4.0"
license_url: "https://creativecommons.org/licenses/by/4.0/"
attribution: "when reusing or quoting, credit the author and link back to the original"
---

# Next.js 13.3.4 Upgrade Gotchas: The Age of Server Components — Migrating from Pages Router to App Router

## Background

To rebuild our German e-commerce site, I did a thorough evaluation and landed on Next.js 13.4.3, the latest stable release at the time. The new SEO metadata API and server-side component model were highly appealing, and since most future Next.js features are being built around the App Router, choosing it was a no-brainer.

## 1. [Server Components](https://nextjs.org/docs/getting-started/react-essentials)

### 1.1 Everything inside the `app` folder is a Server Component by default

- By default, all components on Next.js 13 inside the `app` folder are Server Components. Server Components cannot use client-side features such as `useState`, `useEffect`, etc.
- For third-party components that require client features, the current solution is to create a wrapper component and omit the `'use client'` directive from the wrapper itself.

### 1.2 Benefits of Server Components

Server Components render on the server and ship only the JavaScript bundle that the client actually needs. Unnecessary JS is never downloaded, which reduces network payload and improves performance.

### 1.3 Limitations

Cannot use `useState`, `useReducer`, other Hooks, `useEffect`, or `useLayoutEffect`.

### 1.4 Opting into a Client Component

Add `'use client'` at the top of every file that needs client-side features:

```
"use client";
import xxx
...
```

## 2. New `useRouter`

```
import { useRouter, useParams, usePathname ,useSearchParams } from 'next/navigation';
```

### 2.1 The new `useRouter` no longer exposes `locale`

```
const { locale } = useRouter(); 
```

## 2.2 Routing

### 2.2.1 Folder structure

The new convention makes it easier to distinguish pages from components at a glance:

| URL | Pages Router | App Router |
| -------- | -------- | -------- |
| /     | /page/index.tsx     | /app/page.tsx     |
| /about-us     | /page/about-us.tsx     | /app/about-us/page.tsx     |

P.S. The appendix includes a PowerShell script to quickly migrate from the `pages` folder to the `app` folder.

### 2.2.2 [API Routes](https://nextjs.org/docs/app/building-your-application/routing/router-handlers)

| Page  | Route | Result |
| -------- | -------- | -------- |
| app/page.js     | app/route.js     | Conflict     |
| app/page.js     | app/api/route.js     | Valid     |
| app/[user]/page.js     | app/api/route.js     | Valid     |

```
app/products/api/route.ts
import { NextResponse } from 'next/server';
 
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const id = searchParams.get('id');
  const res = await fetch(`https://data.mongodb-api.com/product/${id}`, {
    headers: {
      'Content-Type': 'application/json',
      'API-Key': process.env.DATA_API_KEY,
    },
  });
  const product = await res.json();
 
  return NextResponse.json({ product });
}
``` 

## 2.3 Changes to Data Fetching

### 2.3.1 No more `getStaticProps` or `getServerSideProps`

```
async func getData() {
  const res = await fetch ("https://api.xxx.com/...");
  return res.json();
}

export default async function About() {
  const name = await getData();
  return "...";
}

```

### 2.3.2 The new `use` hook can unwrap async results

```
use(getData(id))
```

### 2.3.3 Server-side `fetch` is extended with caching by default

The framework extends the native `fetch` API on the server side and adds built-in caching. The official recommendation is to avoid wrapping `fetch` inside Client Components.

### 2.3.4 Rendering strategies via `revalidate`

- **SSG**: pages are statically generated by default.
- **SSR**: declare the component as `async`, set `cache: 'no-store'` / `no-cache`, or set `revalidate` to `0` to opt into dynamic server-side rendering.
- **CSR**: components inside `'use client'` that fetch data inside `useEffect`.
- **ISR**: set `revalidate` in the `fetch` call, or declare it in `page.tsx`:

```
app/page.tsx
export const revalidate = 60; // revalidate this page every 60 seconds
```

P.S. The old `pages/api/revalidate` webhook still works, but it must live in the `pages` folder.

## 3. New [Metadata API](https://nextjs.org/docs/app/building-your-application/optimizing/metadata#static-metadata) — replaces `next/head`

Per-page SEO metadata is now much easier to manage.

### 3.1 Static Metadata

```
import { Metadata } from 'next';
 
export const metadata: Metadata = {
  title: '...',
  description: '...',
};
 
export default function Page() {}
```

### 3.2 Dynamic Metadata

```
import { Metadata, ResolvingMetadata } from 'next';
 
type Props = {
  params: { id: string };
  searchParams: { [key: string]: string | string[] | undefined };
};
 
export async function generateMetadata(
  { params, searchParams }: Props,
  parent?: ResolvingMetadata,
): Promise<Metadata> {
  // read route params
  const id = params.id;
 
  // fetch data
  const product = await fetch(`https://.../${id}`).then((res) => res.json());
 
  // optionally access and extend (rather than replace) parent metadata
  const previousImages = (await parent).openGraph?.images || [];
 
  return {
    title: product.title,
    openGraph: {
      images: ['/some-specific-page-image.jpg', ...previousImages],
    },
  };
}
 
export default function Page({ params, searchParams }: Props) {}
```

## 4. Packages Affected by the Upgrade

### 4.1 i18n (recommended solution)

Right after upgrading, you'll notice that most i18n packages no longer work, and the new `useRouter` doesn't expose `locale` either. After trying several packages, this one worked best:

[Related article](https://next-intl-docs.vercel.app/docs/next-13/server-components)

```
import { useLocale } from 'next-intl';

const locale = useLocale();
```

### 4.2 React Query setup

[Related article](https://codevoweb.com/setup-react-query-in-nextjs-13-app-directory/)

### 4.4 Context API setup

[Related article](https://codevoweb.com/setup-react-context-api-in-nextjs-13-app-directory/)

## 5. [Turbopack](https://nextjs.org/docs/architecture/turbopack) (still in Beta)

Worth keeping an eye on: Next.js 13 ships a new JavaScript bundler called Turbopack, billed as the successor to Webpack. Turbopack is written in Rust and claims to be 700x faster than Webpack (and 10x faster than Vite).

## 6. Conclusion

Next.js updates at a pace that's honestly surprising — a new stable version can drop overnight. Every major release causes a bit of friction, but the upgrade usually takes a day or two, and old patterns are typically preserved alongside the new ones.

The App Router changes are significant and a bit painful, but they formally usher in the era of Server Components.

## Appendix — Quick Migration PowerShell Scripts

### 1. Move all pages from the `pages` folder into the `app` folder

```
# Get a reference to all tsx files in the src\pages directory.
$files = Get-ChildItem -Path "src\pages" -Filter "*.tsx"

# For each file, create a new directory in src\app with the same name as the file (without extension),
# move the file to the new directory, rename it to page.tsx, and prepend 'use client;' to it.
foreach ($file in $files) {
    # Create new directory.
    $newDir = New-Item -Path "src\app\$($file.BaseName)\" -ItemType Directory

    # Move and rename the file.
    $newFile = Move-Item -Path $file.FullName -Destination "$($newDir.FullName)\page.tsx" -PassThru

    # Add 'use client;' to the beginning of the file.
    $content = Get-Content -Path $newFile.FullName
    $newContent = 'use client;' + "`n" + $content
    Set-Content -Path $newFile.FullName -Value $newContent
}

pause
```

### 2. Bulk-convert components to Client Components

```
# Get a reference to all tsx files in the src\pages directory.
$files = Get-ChildItem -Path "src\pages" -Filter "*.tsx"

# For each file, create a new directory in src\app with the same name as the file (without extension),
# move the file to the new directory, rename it to page.tsx, and prepend 'use client;' to it.
foreach ($file in $files) {
    # Create new directory.
    $newDir = New-Item -Path "src\app\$($file.BaseName)\" -ItemType Directory

    # Move and rename the file.
    $newFile = Move-Item -Path $file.FullName -Destination "$($newDir.FullName)\page.tsx" -PassThru

    # Add 'use client;' to the beginning of the file.
    $content = Get-Content -Path $newFile.FullName
    $newContent = 'use client;' + "`n" + $content
    Set-Content -Path $newFile.FullName -Value $newContent
}

pause
```

### 3. Merge all JSON files in a folder into a single JSON file

```
# 初始化空的 Hashtable
$combinedJson = @{}

# 指定資料夾路徑
$folderPath = 'C:\path\to\json\files'

# 尋找所有 .json 檔案
Get-ChildItem -Path $folderPath -Filter *.json | ForEach-Object {
    # 獲取檔案名稱並移除 .json 副檔名
    $propertyName = $_.BaseName

    # 讀取 JSON 檔案內容
    $content = Get-Content $_.FullName | ConvertFrom-Json

    # 將 JSON 內容加入到 combinedJson 中，使用檔案名稱作為屬性
    $combinedJson[$propertyName] = $content
}

# 將合併的 JSON 轉換為字符串並寫入到一個新的 JSON 檔案
$combinedJson | ConvertTo-Json | Set-Content -Path "$folderPath\combined.json"

```

---

## About this article and its author

Originally published on [Mark Ku's Tech Notes](https://blog.markkulab.net/en/post/nextjs-upgrade-app-route)

License: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) — when reusing or quoting, credit the author and link back to the original

### About the author

**[Mark Ku](https://blog.markkulab.net/en/author/mark-ku)** — Software Solution Provider

- 10+ years senior software engineer, now an AI Builder
- Focused on large-platform architecture — North-American e-commerce, AI SaaS subscription billing
- Combining AI Agents and automation to build evolvable product foundations

### Free tools built by the author

All of these are free to use:

- [Free PDF Sign Tool](https://blog.markkulab.net/en/tools/pdf-sign): Online PDF sign tool — draw, type, or upload a signature, then drag, resize, and download. Everything runs in your browser; nothing is uploaded.
- [VS Code Refactory](https://blog.markkulab.net/en/tools/refactory): Refactory is a VS Code refactoring extension: 34 actions plus a 37-rule code-smell inspection layer with a Code Health dashboard, across 18 languages, backed by 534 tests. It learns your repo's conventions: where interfaces live, where DI is registered, whether 'use client' belongs. It ranks files by git churn × complexity so you know what to fix first, and hands any smell to the Claude Code already on your machine. Free to use, and your source never leaves your computer.
- [DB-Kit Database Manager](https://blog.markkulab.net/en/tools/db-kit): DB-Kit is a lightweight, cross-platform database manager built with Tauri + Rust + React. Manage MySQL, MariaDB, PostgreSQL, SQL Server, Oracle, SQLite, MongoDB, Redis, Kafka, Elasticsearch and RabbitMQ from one consistent interface: passwords encrypted in the OS keychain, SSH tunnels, full CRUD, a visual query builder, stacked multi-statement result sets, cross-connection data transfer and compare/sync, Excel / CSV import & export, visualized execution plans, ER diagrams, scheduled backups, SQL stress testing with p50–p99 latency percentiles, a 15-rule SQL review engine, Kafka message browsing with monitoring & alerts, a bilingual UI (Traditional Chinese / English), a built-in AI assistant (natural-language SQL, AI review and tuning advice) and the dbk CLI. Free and open source (MIT), with installers for Windows, macOS and Linux.
- [VS Code Super Mermaid](https://blog.markkulab.net/en/tools/super-mermaid): Super Mermaid is a VS Code extension for beautiful Mermaid diagrams out of the box: auto-colored live preview, mouse pan & zoom, high-res PNG / SVG export, 21 templates and multiple themes. Free and open source (MIT).
- [React Super Mermaid](https://blog.markkulab.net/en/tools/react-super-mermaid): react-super-mermaid is an open-source React component library: render beautiful Mermaid diagrams with a single <MermaidViewer>, with built-in colorful / sketch themes, pan & zoom, in-diagram search, and high-res SVG / PNG export. Lightweight, SSR-safe, fully typed. Free and open source (MIT).
- [Jira / Confluence Super Mermaid](https://blog.markkulab.net/en/tools/jira-super-mermaid): An Atlassian Forge app: write Mermaid syntax directly inside a Jira issue or a Confluence page and get flowcharts, sequence diagrams, state machines and Gantt charts. 11 diagram types, SVG / PNG export, light and dark themes, full CJK support. Runs on Atlassian: your diagrams live in your own site and the app calls no third-party service. Free, coming soon to the Atlassian Marketplace.
- [Mermaid Live Preview](https://blog.markkulab.net/en/tools/mermaid-preview): Write Mermaid in your browser, see it render instantly, and share the whole diagram as a single link. No sign-up, nothing uploaded to a server, and mermaid.live share links work as-is.
- [React Intl Phone Number](https://blog.markkulab.net/en/tools/react-intl-phone-number): react-intl-phone-number is an open-source React component: framework-agnostic and antd-free, with E.164 in/out, a searchable flag / country-code dropdown, configurable validation levels (strict / mobile-strict / loose), themeable CSS, and i18n — phone logic powered by google-libphonenumber. Lightweight and fully typed. Free and open source (MIT).
- [Uptime Kuma Cluster](https://blog.markkulab.net/en/tools/uptime-kuma-cluster): Turn single-node Uptime Kuma into a highly available cluster: OpenResty + Lua smart load balancing, shared MariaDB state, health checks and automatic failover, plus cluster-management REST APIs. One Docker Compose command to start. Free and open source (MIT).
- [Special Education](https://blog.markkulab.net/en/education): Learning materials crafted for special education students

### Daily podcasts

- [Mark's Tech Insights — Daily AI News](https://blog.markkulab.net/en/category/tech-news): Daily curated AI and tech trends. Catch the latest developments via audio summaries — covering AI applications, software architecture, DevOps, and engineering practice. — RSS: https://blog.markkulab.net/feed.xml
- [AI股市蝦聊](https://blog.markkulab.net/en/category/ai-stock-chat): Every trading day, an AI-analyzed take on the Taiwan stock market, delivered as a two-host conversation covering the session and the next-day outlook. — RSS: https://blog.markkulab.net/ai-stock-chat/feed.xml
- [開源好物週報](https://blog.markkulab.net/en/category/open-source-weekly): A weekly two-host pick of free open-source tools surfaced from real Hacker News, GitHub, and Reddit buzz — what pain they solve and the fastest way to get started. — RSS: https://blog.markkulab.net/open-source-weekly/feed.xml

### Newsletter

[Subscribe to the newsletter](https://blog.markkulab.net/en/subscribe) — Be the first to know about new posts. No spam, unsubscribe anytime.
