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
1.1 Everything inside the app folder is a Server Component by default
- By default, all components on Next.js 13 inside the
appfolder are Server Components. Server Components cannot use client-side features such asuseState,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
| 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, setcache: 'no-store'/no-cache, or setrevalidateto0to opt into dynamic server-side rendering. - CSR: components inside
'use client'that fetch data insideuseEffect. - ISR: set
revalidatein thefetchcall, or declare it inpage.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 — 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:
import { useLocale } from 'next-intl';
const locale = useLocale();
4.2 React Query setup
4.4 Context API setup
5. 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"





























Comments