布局和页面
Next.js 使用基于文件系统的路由,这意味着你可以使用文件夹和文件来定义路由。本页将指导你如何创建布局和页面,并在它们之间进行链接。
创建页面
页面是在特定路由上渲染的 UI。要创建页面,在 app 目录内添加一个 page 文件并默认导出一个 React 组件。例如,要创建一个索引页面(/):


- TypeScript
 - JavaScript
 
export default function Page() {
  return <h1>你好 Next.js!</h1>
}
export default function Page() {
  return <h1>你好 Next.js!</h1>
}
创建布局
布局是在多个页面之间共享的 UI。在导航时,布局会保持状态、保持交互性,并且不会重新渲染。
你可以通过从 layout 文件默认导出一个 React 组件来定义布局。该组件应该接受一个 children 属性,该属性可以是页面或另一个布局。
例如,要创建一个接受你的索引页面作为子级的布局,在 app 目录内添加一个 layout 文件:


- TypeScript
 - JavaScript
 
export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        {/* 布局 UI */}
        {/* 在你想渲染页面或嵌套布局的地方放置 children */}
        <main>{children}</main>
      </body>
    </html>
  )
}
export default function DashboardLayout({ children }) {
  return (
    <html lang="en">
      <body>
        {/* 布局 UI */}
        {/* 在你想渲染页面或嵌套布局的地方放置 children */}
        <main>{children}</main>
      </body>
    </html>
  )
}
上面的布局被称为根布局,因为它定义在 app 目录的根目录。根布局是必需的,必须包含 html 和 body 标签。
创建嵌套路由
嵌套路由是由多个 URL 段组成的路由。例如,/blog/[slug] 路由由三个段组成:
/(根段)blog(段)[slug](叶子段)
在 Next.js 中:
- 文件夹用于定义映射到 URL 段的路由节点。
 - 文件(如 
page和layout)用于创建为对应路由节点显示的 UI。 
要创建嵌套路由,你可以在彼此内部嵌套文件夹。例如,要为 /blog 添加路由,在 app 目录中创建一个名为 blog 的文件夹。然后,要使 /blog 公开访问,添加一个 page.tsx 文件:


// 虚拟导入
import { getPosts } from '@/lib/posts'
import { Post } from '@/ui/post'
export default async function Page() {
  const posts = await getPosts()
  return (
    <ul>
      {posts.map((post) => (
        <Post key={post.id} post={post} />
      ))}
    </ul>
  )
}
// 虚拟导入
import { getPosts } from '@/lib/posts'
import { Post } from '@/ui/post'
export default async function Page() {
  const posts = await getPosts()
  return (
    <ul>
      {posts.map((post) => (
        <Post key={post.id} post={post} />
      ))}
    </ul>
  )
}
你可以继续嵌套文件夹来创建嵌套路由。例如,要为特定博客文章创建路由,在 blog 内创建一个新的 [slug] 文件夹并添加一个 page 文件:


- TypeScript
 - JavaScript
 
function generateStaticParams() {}
export default function Page() {
  return <h1>你好, 博客文章页面!</h1>
}
function generateStaticParams() {}
export default function Page() {
  return <h1>你好, 博客文章页面!</h1>
}
用方括号包装文件夹名称(例如 [slug])会创建一个动态路由段,用于从数据生成多个页面。例如博客文章、产品页面等。
嵌套布局
默认情况下,文件夹层次结构中的布局也是嵌套的,这意味着它们通过 children 属性包装子布局。你可以通过在特定路由段(文件夹)内添加 layout 来嵌套布局。
例如,要为 /blog 路由创建布局,在 blog 文件夹内添加一个新的 layout 文件。


- TypeScript
 - JavaScript
 
export default function BlogLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return <section>{children}</section>
}
export default function BlogLayout({ children }) {
  return <section>{children}</section>
}
如果你要组合上面的两个布局,根布局(app/layout.js)将包装 blog 布局(app/blog/layout.js),后者将包装 blog 页面(app/blog/page.js)和博客文章页面(app/blog/[slug]/page.js)。
创建动态段
动态段允许你创建从数据生成的路由。例如,与其为每个单独的博客文章手动创建路由,你可以创建一个动态段来基于博客文章数据生成路由。
要创建动态段,用方括号包装段(文件夹)名称:[segmentName]。例如,在 app/blog/[slug]/page.tsx 路由中,[slug] 是动态段。
- TypeScript
 - JavaScript
 
export default async function BlogPostPage({
  params,
}: {
  params: Promise<{ slug: string }>
}) {
  const { slug } = await params
  const post = await getPost(slug)
  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </div>
  )
}
export default async function BlogPostPage({ params }) {
  const { slug } = await params
  const post = await getPost(slug)
  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </div>
  )
}
嵌套在动态段内的布局也可以访问 params 属性。
使用搜索参数渲染
在服务端组件页面中,你可以使用 searchParams 属性访问搜索参数:
- TypeScript
 - JavaScript
 
export default async function Page({
  searchParams,
}: {
  searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}) {
  const filters = (await searchParams).filters
}
export default async function Page({ searchParams }) {
  const filters = (await searchParams).filters
}
使用 searchParams 会让你的页面选择动态渲染,因为它需要传入请求来读取搜索参数。
客户端组件可以使用 useSearchParams 钩子读取搜索参数。
在静态渲染和动态渲染路由中了解更多关于 useSearchParams 的信息。
何时使用什么
- 当你需要搜索参数来为页面加载数据时使用 
searchParams属性(例如分页、从数据库过滤)。 - 当搜索参数仅在客户端使用时使用 
useSearchParams(例如过滤已通过 props 加载的列表)。 - 作为一个小优化,你可以在回调或事件处理程序中使用 
new URLSearchParams(window.location.search)来读取搜索参数,而不会触发重新渲染。 
在页面之间链接
你可以使用 <Link> 组件在路由之间导航。<Link> 是一个内置的 Next.js 组件,它扩展了 HTML <a> 标签以提供预取和客户端导航。
例如,要生成博客文章列表,从 next/link 导入 <Link> 并向组件传递 href 属性:
- TypeScript
 - JavaScript
 
import Link from 'next/link'
export default async function Post({ post }) {
  const posts = await getPosts()
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.slug}>
          <Link href={`/blog/${post.slug}`}>{post.title}</Link>
        </li>
      ))}
    </ul>
  )
}
import Link from 'next/link'
export default async function Post({ post }) {
  const posts = await getPosts()
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.slug}>
          <Link href={`/blog/${post.slug}`}>{post.title}</Link>
        </li>
      ))}
    </ul>
  )
}
提示:
<Link>是在 Next.js 中路由之间导航的主要方式。你还可以使用useRouter钩子进行更高级的导航。