---
title: 'Next.js من Pages Router إلى App Router: دليل الانتقال لعام'
description: 'دليل عملي لعام 2026: نقل تطبيق Next.js 16 من Pages Router إلى App Router — الـ codemods، والـ async params، والـ route handlers، وفخ الـ legacyBehavior.'
url: https://nerdleveltech.com/ar/nextjs-pages-router-to-app-router-migration-tutorial
language: ar
date: '2026-05-06'
category: frontend
tags:
  - nextjs
  - app-router
  - pages-router
  - migration
  - react
  - typescript
  - server-components
  - frontend
markdown_url: https://nerdleveltech.com/ar/nextjs-pages-router-to-app-router-migration-tutorial.md
attribution: 'Source: NerdLevelTech — https://nerdleveltech.com/ar/nextjs-pages-router-to-app-router-migration-tutorial'
terms: https://nerdleveltech.com/llms.txt
---

# Next.js من Pages Router إلى App Router: دليل الانتقال لعام

## ملخص

يستعرض هذا البرنامج التعليمي كيفية نقل تطبيق حقيقي يعتمد على Pages Router في Next.js (المجلد `pages/`) إلى App Router (المجلد `app/`) على إصدار Next.js 16.2.4[^1] في حوالي خمس وأربعين دقيقة. ستسمح للموجهين (routers) بالتعايش معاً، وتشغيل أداة `@next/codemod` الرسمية لتحويل `params` / `searchParams` / `cookies()` / `headers()` / `draftMode()` من التزامن (sync) إلى عدم التزامن (async)[^2]، ونقل `_app.tsx` و `_document.tsx` إلى ملف `app/layout.tsx` واحد، ونقل معالج `pages/API/*` واحد إلى App Router Route Handler، واستبدال `next/router` بخطافات (hooks) `next/navigation` الثلاثة، وتبديل `getStaticPaths` بـ `generateStaticParams`. بنهاية هذا الدليل، سيكون لديك تطبيق هجين يعمل بالفعل، بالإضافة إلى مسار واضح لحذف مجلد `pages/` عند الانتهاء من نقل آخر مسار.

## ما ستتعلمه

## المتطلبات الأساسية

ثبّت هذه الإصدارات قبل البدء. الإصدارات الأقدم ستظهر أخطاء في واجهة التطبيق الثنائية (ABI) والإعدادات تبدو وكأنها أخطاء في عملية النقل ولكنها في الحقيقة عدم توافق في أدوات التطوير.

- Node.js 20.9.0 أو أحدث (Node.js 18 لم يعد مدعوماً في Next.js 16)[^3]. إصدارات Node.js 22 و 24 LTS كلاهما مناسبان.
- TypeScript 5.1.0 أو أحدث[^3]؛ أحدث إصدار مستقر في سلسلة 5.x أو 6.x يعمل بشكل جيد أيضاً.
- npm 10+ (أو إصدار حديث من pnpm أو yarn) — يتم تشغيل وصفات codemod عبر `npx`.
- مشروع Pages Router موجود حالياً على Next.js 14.x أو 15.x. إذا كنت تستخدم 13.x، فقم بتنفيذ `npm i next@15` أولاً وحل أخطاء الأنواع (type errors) قبل الانتقال إلى App Router؛ خلط ترقيتين رئيسيتين في طلب سحب (PR) واحد هو أسرع طريقة لإضاعة يوم كامل.
- تأكد من نظافة شجرة عمل Git. كل عملية codemod أدناه تقوم بتعديل الملفات؛ ستحتاج إلى التزام (commit) واحد قابل للتراجع لكل خطوة.

## الخطوة 1 — تأكد من أن Next.js 16 هو ما تتوقعه

قم بتحديث التبعيات (dependencies) قبل لمس أي كود مصدري. ستقوم أداة الترقية `@next/codemod` بتعديل `package.json` و `tsconfig.json` وحفنة من ملفات الإعداد نيابة عنك.

```bash
# داخل جذر المشروع
npx @next/codemod@latest upgrade latest
```

هذه العملية تفاعلية — ستسألك عن الإصدار المستهدف (اختر `latest`، والذي يشير إلى 16.2.4 اعتباراً من 15 أبريل 2026[^1]) وعن مدير الحزم الذي ستستخدمه. بعد الانتهاء، تحقق من الإصدارات المثبتة:

```bash
node -p "require('./package.json').dependencies.next"
# "16.2.4"

node -p "require('./package.json').dependencies.React"
# "19.2.5"  — يستخدم App Router واجهة React 19 المستقرة داخلياً[^5]

npx tsc --version
# الإصدار 5.x أو 6.x — أي شيء من 5.1.0 فصاعداً يفي بالحد الأدنى
```

إذا تم تثبيت `next` بإصدار 15.x، فهذا يعني أن أداة الترقية اختارت بصمت تثبيت إصدار محافظ. قم بفرض التحديث يدوياً:

```bash
npm i next@16.2.4 React@19.2 React-dom@19.2
```

لا تقم بتشغيل `next dev` بعد — ستواجه التغييرات الجذرية المتعلقة بـ async-API قبل أن تنتهي من عملية النقل. الخطوة 2 ستعالج ذلك.

## الخطوة 2 — تطبيق codemod لطلبات API غير المتزامنة

قدم إصدار Next.js 15 كلاً من `cookies()` و `headers()` و `draftMode()` و `params` و `searchParams` كدوال غير متزامنة (async) مع توفير طبقة توافق تزامنية. أما إصدار Next.js 16 فقد أزال هذه الطبقة تماماً، لذا فإن أي وصول تزامني سيؤدي إلى خطأ[^2]. تتولى أداة codemod معالجة هذا الأمر آلياً:

```bash
npx @next/codemod@latest next-async-request-API .
```

تقوم الأداة بفحص كل ملف TS/JS في المجلد الحالي وتعديل أربعة أنماط:

- مكونات الخادم (Server Components) التي تقرأ `props.params` مباشرة ستقوم الآن بعمل `await props.params`.
- الصفحات ومعالجات المسارات (Route Handlers) التي تقرأ `searchParams` ستضيف وبالمثل `await`.
- استدعاءات `cookies()` / `headers()` / `draftMode()` من `next/headers` ستصبح `await cookies()` وما شابهها. هذه الواجهات البرمجية الثلاث تعمل على الخادم فقط — استدعها من مكونات الخادم، أو معالجات المسارات، أو إجراءات الخادم (Server Actions). لا يمكن استدعاؤها من مكونات العميل (Client Components)، وداخل البرمجيات الوسيطة (middleware) تستخدم `request.cookies` و `request.headers` على كائن `NextRequest` بدلاً من ذلك[^11].
- مكونات العميل التي تستقبل `params` أو `searchParams` كخصائص (والتي أصبحت الآن من نوع `Promise<...>`) سيتم استخراج قيمتها باستخدام `use()` من React: `const { slug } = use(params)`[^12].

قم بتشغيل `git diff` بعد الـ codemod وألقِ نظرة سريعة. التحويل حتمي ولكنه قد يفشل في مكانين يستحقان الإصلاح اليدوي:

- الدوال التي تقوم بتفكيك (destructure) `params` في توقيعها، مثل `function Page({ params }: { params: { slug: string } })`. عادةً ما تعيد الأداة كتابة التفكيك ولكنها أحياناً تترك تعريف النوع بالشكل التزامني؛ قم بتحديث النوع إلى `Promise<{ slug: string }>`.
- الأماكن التي تم فيها استخدام `params.slug` داخل رد نداء (callback) غير متزامن (مثل سلسلة `.then()` أو تبعيات `useMemo`). ستواجه خطأ TypeScript هناك حتى تقوم برفع `await` إلى أقرب حدود `async`.

بعد التنظيف، قم بتشغيل `npx tsc --noEmit` وأصلح كل خطأ فات الأداة. هذه هي الخطوة الأكثر تأثيراً في عملية النقل بأكملها؛ قم بها قبل إضافة أي ملفات App Router جديدة حتى تتمكن من تحديد سبب أي مشكلة قد تطرأ.

## الخطوة 3 — السماح بتعايش pages/ و app/

يدعم Next.js صراحةً تشغيل كلا الموجهين في نفس المشروع، وقد التزم الفريق بدعم Pages Router على المدى الطويل[^6]. هذا التعايش هو ما يجعل النقل التدريجي آمناً.

أنشئ مجلد `app/` في جذر المشروع (بجانب مجلد `pages/`) وأضف تخطيطاً جذرياً (root layout) مؤقتاً. يحل التخطيط الجذري محل كل من `pages/_app.tsx` و `pages/_document.tsx` بملف واحد[^7]:

```tsx
// app/layout.tsx
import './globals.css';
import type { Metadata } from 'next';

export const metadata: Metadata = {
  title: 'My App',
  description: 'Now serving from app/ and pages/',
};

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

انسخ ملفات CSS العالمية من `pages/_app.tsx` إلى `app/globals.css` (أو أي مسار يشير إليه الاستيراد). انقل أي كتل `<Script strategy="beforeInteractive" />` من `_document.tsx` إلى ملف التخطيط الجذري نفسه — يقوم Next.js تلقائياً بحقن نصوص `beforeInteractive` في وسم `<head>` الخاص بالمستند بغض النظر عن مكان وضع عنصر `<Script>` داخل التخطيط، ولكن ملف التخطيط هو المكان الوحيد المسموح فيه باستخدام هذه الاستراتيجية[^15]. **احتفظ بملفي `_app.tsx` و `_document.tsx` في مكانهما حالياً** — فهما لا يزالان يخدمان المسارات التي لم يتم نقلها بعد.

تحقق من التعايش: قم بتشغيل `npm run dev`، ثم قم بزيارة أي رابط موجود في Pages Router. يجب أن يظهر تماماً كما كان من قبل. لا يتجاوز الموجهان أحدهما الآخر بصمت — إذا كان كل من `app/foo/page.tsx` و `pages/foo.tsx` يشيران إلى `/foo`، فسيفشل بناء Next.js مع ظهور خطأ "Conflicting routes" (مسارات متضاربة)[^13]، لذا فإن نمط النقل الآمن هو حذف ملف `pages/foo.tsx` القديم في نفس الالتزام (commit) الذي يضيف `app/foo/page.tsx`.

## الخطوة 4 — نقل أول مسار نهائي (Leaf Route)

اختر مسارًا منخفض المخاطر لترحيله أولاً — صفحة "من نحن" (About) ثابتة، أو عرض قائمة لا يستخدم `getServerSideProps`، أو صفحة تفاصيل يتم جلب بياناتها مرة واحدة. النمط دائمًا هو:

1. إنشاء `app/<route>/page.tsx`.
2. نقل الـ JSX إليه.
3. استبدال دوال جلب البيانات المساعدة بما يعادلها في App Router.
4. حذف ملف `pages/<route>.tsx` القديم في النهاية، بعد أن يعمل المسار الجديد بشكل صحيح.

إليك صفحة ثابتة بنظام Pages Router:

```tsx
// pages/blog/[slug].tsx — قبل
import type { GetStaticPaths, GetStaticProps } from 'next';

type Props = { post: { title: string; body: string } };

export const getStaticPaths: GetStaticPaths = async () => {
  const slugs = await fetch('https://api.example.com/posts').then(r => r.json());
  return {
    paths: slugs.map((s: string) => ({ params: { slug: s } })),
    fallback: 'blocking',
  };
};

export const getStaticProps: GetStaticProps<Props> = async ({ params }) => {
  const post = await fetch(`https://api.example.com/posts/${params!.slug}`).then(r => r.json());
  return { props: { post }, revalidate: 60 };
};

export default function PostPage({ post }: Props) {
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.body}</p>
    </article>
  );
}
```

ما يعادلها في App Router أقصر ويتم تصييره بالكامل على الخادم:

```tsx
// app/blog/[slug]/page.tsx — بعد
export const revalidate = 60; // ISR على مستوى الجزء (segment)

export async function generateStaticParams() {
  const slugs: string[] = await fetch('https://api.example.com/posts').then(r => r.json());
  return slugs.map(slug => ({ slug }));
}

export default async function PostPage({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
  const post = await fetch(`https://api.example.com/posts/${slug}`).then(r => r.json());

  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.body}</p>
    </article>
  );
}
```

ثلاث قواعد تغطي تقريبًا كل حالة. تصبح `getStaticProps` مجرد `await fetch` عادية داخل Server Component من نوع `async`، مع التعامل مع الصفحة كثابتة افتراضيًا. وتتحول `getStaticPaths` إلى `generateStaticParams`، والتي تعيد مصفوفة مسطحة من كائنات المعاملات بدلاً من غلاف `{ params: { ... } }` القديم[^8]. أما `getServerSideProps` فتصبح إما `export const dynamic = 'force-dynamic'` في أعلى الملف، أو خيار `cache: 'no-store'` لكل عملية جلب — وكلاهما يعطل التخزين المؤقت.

بعد أن يتم تصيير الملف الجديد بشكل صحيح على نفس الرابط، قم بتنفيذ `git rm pages/blog/[slug].tsx`. كرر ذلك مع المسار التالي.

## الخطوة 5 — نقل مسار API واحد إلى Route Handler

تتبع مسارات API نفس قواعد التعايش. يستمر `pages/api/users/[id].ts` في العمل حتى تقوم بإنشاء `app/api/users/[id]/route.ts`. الشكل الجديد يستخدم تصديرات بأسماء طرق HTTP ودوال `NextRequest` / `NextResponse` المساعدة[^9]:

```ts
// pages/api/users/[id].ts — قبل
import type { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method !== 'GET') {
    return res.status(405).json({ error: 'Method not allowed' });
  }
const { id = req.query;
  const user = await fetch(`https://api.example.com/users/${id}`).then(r => r.json());
  return res.status(200).json(user);
}
```

```ts
// app/api/users/[id]/route.ts — بعد
import { NextRequest, NextResponse } from 'next/server';

export async function GET(
  _request: NextRequest,
  { params : { params: Promise<{ id: string }> ,
) {
  const { id = await params;
  const user = await fetch(`https://api.example.com/users/${id}`).then(r => r.json());
  return NextResponse.json(user, { status: 200 );
}
```

لاحظ ثلاثة تغييرات تواجه معظم الفرق. لم يعد الـ handler تصديرًا افتراضيًا (default export) — فكل طريقة HTTP مدعومة تحصل على تصدير خاص بها باسمها `async function GET / POST / PUT / PATCH / DELETE / HEAD / OPTIONS`، والطرق غير المدعومة تعيد 405 تلقائيًا. الجزء الديناميكي أصبح الآن `Promise`، تمامًا كما في الصفحات، لأن نفس قاعدة طلبات API غير المتزامنة تنطبق هنا. وتم استبدال `res.status().json()` بـ `return NextResponse.json(body, { status })`، وهو ما يتوافق بشكل أفضل مع `revalidate`، والترويسات (headers)، واستجابات البث (streaming responses).

تحقق باستخدام curl من نافذة أوامر أخرى:

```bash
curl -i http://localhost:3000/api/users/42
# HTTP/1.1 200 OK
# content-type: application/json
# {"id":42,"name":"Ada Lovelace"}
```

## الخطوة 6 — استبدال next/router بـ next/navigation

أي Client Component يستورد `next/router` سيستمر في العمل فقط طالما يتم تصيير هذا المكون من خلال مسار في `pages/`. بمجرد نقله تحت `app/`، يجب أن ينتقل إلى `next/navigation` ويتم تقسيم الاستيراد إلى ثلاثة خطافات (hooks)[^10]:

```tsx
// قبل — pages/<anywhere>.tsx
'use client';
import { useRouter } from 'next/router';

export function SortToggle() {
  const router = useRouter();
  const sort = (router.query.sort as string) ?? 'newest';
  return (
    <button onClick={() => router.push({ query: { sort: 'oldest' } })}>
      Sort: {sort}
    </button>
  );
}
```

```tsx
// بعد — تحت app/
'use client';
import { useRouter, usePathname, useSearchParams } from 'next/navigation';

export function SortToggle() {
  const router = useRouter();
  const pathname = usePathname();
  const searchParams = useSearchParams();
  const sort = searchParams.get('sort') ?? 'newest';

  const flip = () => {
    const next = new URLSearchParams(searchParams);
    next.set('sort', 'oldest');
    router.push(`${pathname?${next.toString()`);
  };

  return <button onClick={flip>Sort: {sort</button>;
}
```

جدول التحويل قصير ويستحق الحفظ: يصبح `router.pathname` هو `usePathname()`، ويصبح `router.query` لسلاسل الاستعلام (query strings) هو `useSearchParams()`، ويصبح `router.query` للأجزاء الديناميكية هو `useParams()`. جميع الخطافات الثلاثة مخصصة لـ Client Components فقط وتتطلب `'use client'` في أعلى الملف. لا تزال `router.push` / `router.replace` موجودة في `useRouter()` من `next/navigation` ولكنها تقبل رابط URL نصي بدلاً من كائن استعلام — قم ببناء الرابط بنفسك باستخدام `URLSearchParams`.

## الخطوة 7 — إصلاح فخ legacyBehavior في Link

أسقط React 16 دعم خاصية `legacyBehavior` في `<Link>` تمامًا[^4]. أي كود لا يزال يغلف `<a>` داخل `<Link legacyBehavior>` سيتم بناؤه بنجاح في الإصدار 15.x ولكنه سيتعطل بمجرد الانتقال إلى 16.0. الإصلاح آلي:

```tsx
// قبل
<Link href="/about" legacyBehavior>
  <a className="nav-link">About</a>
</Link>

// بعد
<Link href="/about" className="nav-link">About</Link>
```

أصبحت أسماء الفئات (Class names)، وخصائص `target`، و `rel` توضع الآن على `<Link>` نفسه (حيث يقوم بتصيير `<a>` تلقائيًا). المكونات المخصصة التي كانت تغلف رابطًا وتستخدم `passHref` تحتاج إلى أكبر قدر من التفكير — فبدون `legacyBehavior`، غالبًا ما ستنتهي بتصيير زوج غير صالح من `<a><a></a></a>`. الإصلاح الأنظف هو وضع `<Link>` خارج الغلاف وتنسيق العنصر الداخلي بدلاً من ذلك.

يمكن لعملية بحث سريعة (grep) العثور على كل الحالات المخالفة:

```bash
grep -rn "legacyBehavior" app/ pages/ components/
```

يوجد أيضًا codemod مخصص يتعامل مع الحالات البسيطة:

```bash
npx @next/codemod@latest new-link .
```

يقوم الـ codemod بإعادة كتابة الأغلفة الواضحة تلقائيًا؛ أما أغلفة المكونات المخصصة فلا تزال بحاجة إلى مراجعة يدوية. أي شيء يتبقى بعد هاتين الخطوتين يجب إعادة كتابته قبل أن ينجح أمر `next build`.

## التحقق

قم بتشغيل اختبارات التحقق الثلاثة للترحيل بالترتيب:

```bash
# 1. فحص الأنواع (Type check) يمر — يكتشف تحويلات API غير المتزامنة المفقودة
npx tsc --noEmit

# 2. فحص الكود (Lint) يمر — يكتشف استيرادات next/router الضالة تحت app/
npx next lint

# 3. بناء الإنتاج ينجح — يكتشف legacyBehavior والتغييرات الأخرى الخاصة بالإصدار 16
npm run build

# 4. تجربة مسار تم ترحيله بالكامل
npm start &
curl -s http://localhost:3000/blog/hello-world | grep -c "<article>"
# المتوقع: 1
```

إذا فشل `next build` مع شكوى من فقدان معامل `slug` لـ `/blog/[slug]`، فمن المرجح أن `generateStaticParams` قد أعادت شكل الغلاف القديم — قم بتبسيطه إلى `[{ slug: 'foo' }]`، وليس `[{ params: { slug: 'foo' } }]`[^8].

## استكشاف الأخطاء وإصلاحها

**خطأ "Dynamic APIs are Asynchronous"** (صفحة التوثيق موجودة في `nextjs.org/docs/messages/sync-dynamic-apis`)[^14] — الـ codemod فاته أحد أماكن الاستدعاء. أضف `await` في Server Components، و Route Handlers، و Server Actions. بالنسبة لخصائص `params` / `searchParams` في Client Components، قم بفكها باستخدام `use()` من React. هذا هو الخطأ الأكثر شيوعًا بعد الخطوة 2.

**خطأ من نوع `useRouter is not a function` داخل `app/`** — لقد قمت بالاستيراد من `next/router`. انتقل إلى `import { useRouter } from 'next/navigation'`. يتشارك خطافا `useRouter` في الاسم ولكن لهما أشكال إرجاع مختلفة؛ الخلط بينهما يؤدي إلى تعطل وقت التشغيل مع رسالة خطأ مضللة.

**عدم تطابق الترطيب (Hydration mismatch) بعد نقل مزودي `_app.tsx` إلى `app/layout.tsx`** — يجب تشغيل مزودي سياق (Context Providers) React على العميل، ولكن `app/layout.tsx` هو Server Component افتراضيًا. قم بتغليف المزودين في مكون منفصل من نوع `'use client'` (مثلاً `app/providers.tsx`) واستورده في التخطيط الجذري (root layout).

**فشل البناء مع رسالة حول إزالة/إهمال `legacyBehavior`** — أكمل الخطوة 7 قبل إعادة المحاولة. يشير الخطأ إلى مسار ملف؛ أعد تشغيل grep من الخطوة 7 إذا كان هناك أي شيء لا يزال مفقودًا.

**ISR لا يتحدث بعد تعديل `revalidate`** — ذاكرة التخزين المؤقت للبناء تحت `.next/cache` تبقى عبر عمليات `next build`. احذف `.next/cache` وأعد البناء؛ نافذة إعادة التحقق (revalidate window) الجديدة تنطبق فقط على الصفحات التي تم بناؤها بعد التغيير.

## الخطوات التالية

بمجرد أن تصبح كل المسارات موجودة تحت `app/`، قم بحذف `pages/_app.tsx`، و `pages/_document.tsx`، ومجلد `pages/` الذي أصبح فارغاً الآن في "commit" نهائي للتنظيف. من هنا، يفتح App Router بقية مميزات Next.js 16: البث (streaming) باستخدام `loading.tsx`، والمسارات المتوازية (parallel routes)، والمسارات الاعتراضية (intercepting routes)، وبدائيات التخزين المؤقت (caching primitives) الجديدة. لمزيد من أنماط App Router المتقدمة على نطاق واسع، راجع [إتقان أنماط Next.js App Router لتطبيقات الويب القابلة للتوسع](https://nerdleveltech.com/mastering-nextjs-app-router-patterns-for-scalable-web-apps)؛ وللتعرف على نموذج الرندر الذي يجعل Server Components تتألق، راجع [React Server Components: مستقبل الرندر السلس](https://nerdleveltech.com/React-server-components-the-future-of-seamless-rendering)؛ وإذا كنت تضيف ميزات الذكاء الاصطناعي فوق التطبيق الذي تم ترحيله، فإن [إتقان Vercel AI SDK v6](https://nerdleveltech.com/mastering-vercel-ai-sdk-v6-building-smarter-scalable-ai-apps) يربط بدائيات البث الجديدة مع App Router.

[^1]: Next.js 16 release blog and 16.2 release notes — <https://nextjs.org/blog/next-16> and <https://nextjs.org/blog/next-16-2>

[^2]: "Dynamic APIs are Asynchronous" + Codemods reference — <https://nextjs.org/docs/messages/sync-dynamic-apis> and <https://nextjs.org/docs/app/guides/upgrading/codemods>

[^3]: Next.js 16 upgrade guide (Node.js 20.9 minimum, TypeScript 5.1 minimum) — <https://nextjs.org/docs/app/guides/upgrading/version-16>

[^11]: `cookies()` API reference (Server Components / Server Functions / Route Handlers / middleware) — <https://nextjs.org/docs/app/API-reference/functions/cookies>

[^12]: `page.js` file-system convention reference (Promise-typed `params` / `searchParams` and `use()` unwrapping in Client Components) — <https://nextjs.org/docs/app/API-reference/file-conventions/page>

[^6]: Vercel team statement on long-term Pages Router support — <https://GitHub.com/vercel/Next.js/discussions/56655>

[^7]: "Migrating: App Router" official guide (root layout replaces \_app + \_document) — <https://nextjs.org/docs/app/guides/migrating/app-router-migration>

[^15]: `next/script` Component reference (`beforeInteractive` placement rules) — <https://nextjs.org/docs/app/API-reference/components/script>

[^13]: Next.js project-structure reference (conflicting routes between `app/` and `pages/` cause a build-time error) — <https://nextjs.org/docs/app/getting-started/project-structure>

[^8]: `generateStaticParams` API reference — <https://nextjs.org/docs/app/API-reference/functions/generate-static-params>

[^9]: "Getting Started: Route Handlers" — <https://nextjs.org/docs/app/getting-started/route-handlers>

[^10]: `useRouter` (App Router) reference + GitHub Discussion #48426 on the `next/router` vs `next/navigation` split — <https://nextjs.org/docs/app/API-reference/functions/use-router> and <https://GitHub.com/vercel/Next.js/discussions/48426>

[^4]: GitHub Discussion #80179, "NextLink legacyBehavior deprecated and removed in next 16" — <https://GitHub.com/vercel/Next.js/discussions/80179>

[^14]: "Dynamic APIs are Asynchronous" error reference — <https://nextjs.org/docs/messages/sync-dynamic-apis>
