Next.js 布局和嵌套布局指南
作者:Ibadehin Mojeed ✏️
在使用 Next.js 构建项目时,我们通常通过组装独立的组件来创建整个用户界面。
然而,界面某些部分需要在多个路由中使用相同的代码片段——例如导航栏的头部、底部和侧边栏。为了解决这个问题,我们使用布局来构建界面结构,从而实现代码片段的共享。
在本课中,我们将深入探讨如何使用页面路由和应用路由在 Next.js 中管理布局和嵌套布局。我们将涵盖以下内容:
请查看我制作的演示项目,了解其实际效果:
您也可以在 GitHub 上查看项目源代码。以下是项目的布局详情:
- 和
Home路由/dashboard/*共享页眉和页脚内容 - 该
Newsletter路由的页脚内容不同,且没有页眉。 - 这些
/dashboard/*路由实现了嵌套布局,共享侧边栏导航。
要学习本教程,您需要对 Next.js 有一定的了解。让我们开始吧!
使用 Next.js Pages Router 管理布局
Next.js 建议使用 App Router 创建新项目。但是,在本教程中,我们还将讨论如何使用 Pages Router 实现布局和嵌套布局,以便尚未迁移到新的 Next.js 路由系统的用户也能理解。
为了帮助说明这两种方法之间的区别,我们将使用这两种方法创建同一个应用程序,并比较新的应用程序路由器如何简化嵌套布局的实现过程。
首先,我们来看一下 Next.js 中 Pages Router 的典型文件夹结构:
...
├── components
│ ├── Footer.js
│ └── Header.js
├── pages
│ ├── dashboard
│ │ ├── account.js
│ │ ├── analytics.js
│ │ └── settings.js
│ ...
│ ├── index.js
│ └── newsletter.js
...
要使用 Pages 路由系统定义布局,我们创建一个Layout组件来渲染任何共享的用户界面及其子组件。
创建components/Layout.js文件并渲染共享的页眉和页脚内容:
import Header from './Header';
import Footer from './Footer';
const RootLayout = ({ children }) => {
return (
<>
<Header />
<main>{children}</main>
<Footer />
</>
);
};
export default RootLayout;
该Layout组件接受一个childrenprop,该 prop 用作当前页面内容的占位符。
在最终项目中,我们使用Tailwind CSS 进行样式设置。因此,更新后的标记包含类实用程序:
const RootLayout = ({ children }) => {
return (
<div className="flex flex-col min-h-screen mx-auto max-w-2xl px-4 pt-8 pb-16">
<div className="flex-grow">
<Header />
<main className="my-0 py-16">{children}</main>
</div>
<Footer />
</div>
);
};
初学者可以采用的方法是将每个页面渲染后的标记用<RootLayout>组件包裹起来。例如,包裹Home组件的渲染部分如下所示:
import RootLayout from '@/components/Layout';
const Home = () => {
return (
<RootLayout>
<main>{/* ... */}</main>
</RootLayout>
);
};
export default Home;
这样做可以实现我们想要的页面用户界面:
但是,这种实现方式无法在页面切换之间保留状态。例如,在布局相同的页面之间导航时,搜索框的输入文本会被清除。这并非我们期望的单页应用程序体验。
下一节,我们将讨论如何在共享布局中保存状态。
在 Next.js Pages Router 中持久化布局
如果我们检查pages/_app.jsNext.js 在每个页面初始化期间调用的文件,我们会看到一个App包含Component表示当前活动页面的属性的组件:
import '@/styles/globals.css'
export default function App({ Component, pageProps }) {
return <Component {...pageProps} />
}
在这个文件中,我们可以加载共享布局和其他全局文件,例如全局 CSS 文件。所以,让我们<RootLayout>像这样用以下代码包裹页面内容:
import '@/styles/globals.css';
import RootLayout from '@/components/Layout';
export default function App({ Component, pageProps }) {
return (
<RootLayout>
<Component {...pageProps} />
</RootLayout>
);
}
通过这种实现方式,RootLayout组件可以在页面切换之间重用。因此,共享组件中的状态(例如状态栏Header)将被保留。
我们不再需要用<RootLayout>组件包裹每个页面的渲染。
保存所有文件并重新访问应用程序后,我们可以在搜索字段中输入内容,并看到状态现在在页面更改之间得以保留,这是一个改进!
请注意,如果开发服务器运行不正常,您可能需要重启它。
使用 Next.js Pages Router 创建嵌套布局
要创建嵌套共享布局(如页面所示/dashboard/*),我们需要在根布局中嵌套一个渲染侧边栏导航的新布局。
然而,在当前的实现方式下,仅仅将活动路由包装在根布局中(就像我们在pages/_app.js文件中所做的那样)只有在我们需要为整个应用程序使用一个布局时才有效。
为了实现嵌套布局,Next.js 提供了一种逐页组合布局的方法。
getLayout使用以下功能组合每页布局
借助 Next.js 的 pages 目录,我们可以创建多个布局并进行嵌套,或者使用页面级实现创建应用于特定路由的自定义布局。这意味着,pages/_app.js我们不再需要在文件中渲染根布局,而是让每个页面组件负责其自身的全部布局。
我们先从页面开始。我们可以通过在页面组件上Home应用一个属性来实现每个页面的布局:getLayout
import RootLayout from '@/components/Layout';
const Home = () => {
return <main>{/* ... */}</main>;
};
Home.getLayout = (page) => {
return <RootLayout>{page}</RootLayout>;
};
export default Home;
我们定义了一个函数,它以当前页面作为参数,并返回首页所需的 UI。请注意,我们不必使用名称getLayout——它可以是任何名称。
现在我们可以在pages/_app.js文件中调用该函数,并将当前页面作为参数传递。为此,我们将按如下方式修改App组件pages/_app.js:
export default function App({ Component, pageProps }) {
// If page layout is available, use it. Else return the page
const getLayout = Component.getLayout || ((page) => page);
return getLayout(<Component {...pageProps} />);
}
Next.js 初始化页面时,会使用函数检查页面组件中是否定义了页面布局getLayout。如果定义了布局,则使用该布局渲染页面;否则,页面将按原样渲染。
文件保存后,Home页面现在应该会按照指定的布局进行渲染。
创建嵌套DashboardLayout
为了在路由段下创建嵌套页面布局/dashboard/*,我们需要创建一个名为 `<link />` 的新布局文件components/DashboardLayout.js。该文件应导出一个组件,该组件返回这些页面的共享 UI,并使用children`<prop>` 属性来渲染它们各自的内容:
const DashboardLayout = ({ children }) => {
return (
<div className="flex gap-8">
<aside className="flex-[2]">
{/* Include shared UI here e.g. a sidebar */}
</aside>
<div className="bg-gray-100 flex-[8] p-4 rounded min-h-[300px]">
{children}
</div>
</div>
);
};
export default DashboardLayout;
现在,在每个/dashboard/*页面文件中,我们需要getLayout对页面组件应用一个属性,并返回所需的布局树。
例如,该/dashboard/account.js文件将如下所示:
import RootLayout from '@/components/Layout';
import DashboardLayout from '@/components/DashboardLayout';
const Account = () => {
return <div>Account screen</div>;
};
Account.getLayout = (page) => (
<RootLayout>
<DashboardLayout>{page}</DashboardLayout>
</RootLayout>
);
export default Account;
注意它DashboardLayout是如何嵌套在内的RootLayout。
如果我们把这个getLayout属性应用到路由下的其他页面组件/dashboard/*,我们也能得到想要的布局,在页面转换之间保持状态:
查看GitHub 上其他页面组件的文件,仔细检查一下你目前的工作。
在 Next.js Pages Router 中创建自定义布局
您可以像我们Newsletter在最终项目页面上所做的那样,创建一个自定义布局。该布局会呈现不同的页脚内容,并且没有导航栏或侧边栏。
我们将创建一个新的布局文件components/OnlyFooterLayout.js,并返回自定义的页脚和children属性。该组件的代码如下所示:
import NewsletterFooter from './NewsletterFooter';
const OnlyFooterLayout = ({ children }) => {
return (
<div className="flex flex-col min-h-screen mx-auto max-w-2xl px-4 pt-8 pb-16">
<div className="flex-grow">
<main className="my-0 py-16">{children}</main>
</div>
<NewsletterFooter />
</div>
);
};
export default OnlyFooterLayout;
接下来,我们将创建components/NewsletterFooter.js文件并渲染一些自定义页脚内容:
const NewsletterFooter = () => {
return (
<footer className="flex items-center justify-between">
{/* ... */}
</footer>
);
};
export default NewsletterFooter;
最后,在文件中,我们将对页面组件pages/newsletter.js应用一个属性,然后返回页面所需的 UI:getLayoutNewsletter
import OnlyFooterLayout from '@/components/OnlyFooterLayout';
export const Newsletter = () => {
return (
// ...
);
};
Newsletter.getLayout = (page) => (
<OnlyFooterLayout>{page}</OnlyFooterLayout>
);
export default Newsletter;
如果保存所有文件,页面现在应该会以自定义布局呈现。
请查看项目源代码。
在 Next.js 应用路由器中管理布局
Next.js 13 引入了 App Router 文件系统,它为布局、嵌套路由和嵌套布局提供了一流的支持。从 13.4 版本开始,我们可以在生产环境中安全地使用这个新的路由系统。
根据我们的项目路径,app目录结构大致如下:
...
├── app
│ ...
│ ├── dashboard
│ │ ├── account
│ │ │ └── page.js
│ │ ├── analytics
│ │ │ └── page.js
│ │ └── settings
│ │ └── page.js
│ ├── newsletter
│ │ └── page.js
│ ├── layout.js
│ └── page.js
├── components
│ ...
│
...
目录中的每个文件夹或嵌套文件夹app都定义了一条路由或嵌套路由,并且需要一个特殊的page.js文件来渲染其各自的用户界面。
layout.js顶层文件是应用程序所有页面共享的根布局文件。该文件是必需的,默认情况下具有以下结构:
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
Next.js 使用此根布局来包装页面内容或渲染期间可能存在的任何嵌套布局。
与我们在目录中所做的类似pages,我们也可以像这样将顶级共享组件包含在此根布局中:
import Header from '@/components/Header';
import Footer from '@/components/Footer';
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<div className="flex flex-col min-h-screen mx-auto max-w-2xl px-4 pt-8 pb-16">
<div className="flex-grow">
<Header />
<main className="my-0 py-16">{children}</main>
</div>
<Footer />
</div>
</body>
</html>
);
}
如预期,此布局将在不同的路由中保持不变,并维持组件状态。
需要注意的是,该app目录代表根部分,因此该app/page.js文件将渲染索引页面的用户界面:
const Home = () => {
return <main>{/* ... */}</main>;
};
export default Home;
值得注意的是,该app目录中的组件默认是React 服务器组件,这与 pages 目录中的组件不同。因此,我们无法在这些组件中使用客户端钩子。
因此,为了解决这个问题,我们从组件中提取了活动菜单类和搜索功能的逻辑(分别利用了usePathname客户useState端钩子),Header并将它们放置在单独的客户端组件中。
在 Next.js 应用路由中创建嵌套布局
要创建嵌套共享布局,特别是针对/dashboard/*路由段下的页面,我们只需要在文件夹layout.js内添加一个文件dashboard来定义用户界面:
const DashboardLayout = ({ children }) => {
return (
<div className="flex gap-8">
<aside className="flex-[2]">
{/* Include shared UI here e.g. a sidebar */}
</aside>
<div className="bg-gray-100 flex-[8] p-4 rounded min-h-[300px]">
{children}
</div>
</div>
);
};
export default DashboardLayout;
此布局将嵌套在根布局中,并将包裹路由段中的所有页面/dashboard/*,为这些页面提供更具体、更有针对性的用户界面。
就这些了。正如我们所见,在应用路由中创建嵌套布局非常简单直观。
在 Next.js 应用路由器中创建自定义布局
为了设计个性化的页面布局Newsletter,我们必须将该特定路由段与共享布局隔离开来。为此,我们将使用路由组。
创建路由组
路由组是一种将相关路由分组的方法。在我们的项目中,我们将创建两个路由组:
custom包含该路线的路线组:Newsletter此路线组的布局将呈现自定义布局,不包含导航栏或侧边栏,并且页脚也不同。- 路由
primary组包含索引路由和所有/dashboard/*路由,因为它们共享相同的根布局。
请注意,我们可以随意命名路由组。这仅仅是为了便于组织。
要创建路由组,我们需要将组名用括号括起来。如果我们把目录重新组织app成两个组,结构如下:
...
├── app
│ ├── (primary)
│ │ ├── dashboard
│ │ │ ├── account
│ │ │ │ └── page.js
│ │ │ ├── analytics
│ │ │ │ └── page.js
│ │ │ ├── settings
│ │ │ │ └── page.js
│ │ │ └── layout.js
│ │ ├── layout.js
│ │ └── page.js
│ ├── (custom)
│ │ ├── newsletter
│ │ │ └── page.js
│ │ └── layout.js
│ └── layout.js
│ ...
│
...
每个分组都有各自的布局,方便我们根据需要自定义用户界面。(primary)/layout.js现在看起来是这样的:
export default function MainLayout({ children }) {
return (
<div className="flex flex-col min-h-screen mx-auto max-w-2xl px-4 pt-8 pb-16">
<div className="flex-grow">
<Header />
<main className="my-0 py-16">{children}</main>
</div>
<Footer />
</div>
);
}
与此同时,(custom)/layout.js看起来是这样的:
import NewsletterFooter from '@/components/NewsletterFooter';
export default function CustomLayout({ children }) {
return (
<div className="flex flex-col min-h-screen mx-auto max-w-2xl px-4 pt-8 pb-16">
<div className="flex-grow">
<main className="my-0 py-16">{children}</main>
</div>
<NewsletterFooter />
</div>
);
}
最后,顶层app/layout.js文件现在应该包含如下的`<head> <html>` 和 `<body>`标签:<body>
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
如果保存所有文件,每个路由都应该能按预期渲染!请在 GitHub 上查看完整的项目源代码。
结论
理解 Next.js 中的布局机制对于使用该框架构建复杂项目至关重要。本指南涵盖了构建包含共享内容的渲染 UI 并将其应用于多个路由的所有必要步骤。
我们已经讨论了如何在页面路由和新的应用路由中实现布局和嵌套布局。此外,我们还了解了如何使用路由组为特定的路由段创建自定义布局。
如果您觉得本指南有用,欢迎分享给其他人。如有任何疑问或建议,请留言。
文章来源:https://dev.to/logrocket/a-guide-to-nextjs-layouts-and-nested-layouts-5c0d