App Router 与路由基础

2026年9月6日

App Router 是约定式路由:app 里每个文件夹是一段 path,框架按文件名决定这段路由长什么样。不用手写路由表。


文件系统怎么对应 URL

app 下的目录名就是 URL 片段,真正被当成页面的是 page.tsx

app/
├── page.tsx              → /
├── about/page.tsx        → /about
├── blog/test/page.tsx    → /blog/test
└── contact/page.tsx      → /contact
text

只有文件夹、没有 page.tsx,这条路径不会变成可访问页面,但仍可以放 layout、共享组件。


page

page.tsx 是这条路由的叶子。默认是服务端组件,可以直接 async、读文件、打接口。需要点击、输入时,再拆出带 'use client' 的子组件。


layout 和 template

两者都包一层 UI,区别在切页时状态还在不在

layouttemplate
用途导航、侧栏、页脚等共享壳每次进入都要重置的外壳
切到兄弟路由状态保留重新挂载,状态清掉
根布局app/layout.tsx 必须存在可选

多层可以嵌套。同一段同时有 layout 和 template 时,顺序是 layout → template → page

// app/blog/layout.tsx
export default function BlogLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <section>
      <h1>Blog</h1>
      {children}
    </section>
  );
}
tsx

如果外壳上有计数器、输入框,用 layout 时在 /blog/a/blog/b 之间跳,数字还在;换成 template.tsx,跳一次就归零。要在外壳里用 useState,文件顶部加 'use client'


loading

同级放 loading.tsx,这段路由里有未完成的 await 时,Next 会用 React Suspense 先画出它。

// app/blog/loading.tsx
export default function Loading() {
  return <p>加载中…</p>;
}
tsx

页面里拖一段时间:

async function getData() {
  await new Promise((r) => setTimeout(r, 2000));
  return "ok";
}

export default async function Page() {
  const data = await getData();
  return <p>{data}</p>;
}
tsx

loading.tsx 覆盖的是这一段路由,不是整个站点。更细的粒度可以在页面里自己包 Suspense


error

error.tsx 是这段路由的 Error Boundary,必须是客户端组件

"use client";

export default function Error({
  error,
  reset,
}: {
  error: Error;
  reset: () => void;
}) {
  return (
    <div>
      <p>{error.message}</p>
      <button onClick={() => reset()}>重试</button>
    </div>
  );
}
tsx

渲染树里抛错会落到最近的 error.tsx。根布局自己炸了要用 global-error.tsx,那是另一层。


not-found

框架自带 404。要改文案,在 app/not-found.tsx(或某段路由下)写自己的页面。主动触发:

import { notFound } from "next/navigation";

export default async function Page() {
  const post = await findPost();
  if (!post) notFound();
  return <article>{post.title}</article>;
}
tsx

一张对照表

文件作用
page.tsx页面本身
layout.tsx共享壳,切页保状态
template.tsx类似壳,切页重建
loading.tsx异步等待时的占位
error.tsx这段路由的错误边界
not-found.tsx404

下一篇写怎么跳:LinkuseRouterredirect


参考文档