nextjs

路由的定义

  • 首先路由必须包含在app文件的内部
  • 路由文件的后缀为.js或者为.jsx
  • 每个文件代表了一段路由

当前页面为根目录下的启动页面,当对其进行修改时,就可以展现出不同的启动效果

因此我们这里要记住的第一个关键路由——概念页面,应用程序文件夹内的tsx文件会自动映射跟目录
当你想要展示app/about/page,就可以直接在导航栏地址上,通过后缀为about就可以显示其文件夹下的page页面效果,而通常也把这种页面结构理解为 根/段/叶

嵌套路由

当我们需要在一个路径下去通过叶节点来切换页面的时候,这个时候就会引用到嵌套路由。因此当我们使用嵌套路由的时候段节点是不会发生改变的,例如

这里我们就可以看出,文件的结构依旧为文件的嵌套效果,这里我们看以下他本身的目录结构,因此我们可以得知nextjs本身会把目录结构映射到url当中

动态路由

很显然当我们要处理多个页面的时候,利用嵌套路由是并不方便的。因此这里会讲到动态的路由,当你在product的url中,可以看到对应的多个产品列表,当你点击产品时候就会出现对应的产品信息。这样就使得我们的页面功能上更加的完善

这里也就是我们第一product页面,我们假设列表的页面是多于三个,这个时候要是使用传统的嵌套也会让我们的工作量变大,因此这里开始运用动态路由

动态路由是在文件夹的命名用[]包裹 ,例如 [blog]

因此我们接下来会做一个基本的案例,当product叶节点发生改变时,所有展示的产品细节也会添加上列表的编号

   
import React from 'react';

export default async function ProductDetails({

 params,

}: {   
params: Promise <{ productId: string }>;

}){
  const  productId = (await params).productId;
return <React.Fragment>Product Details {productId}</React.Fragment>;
} 

接下来我们看一下实现的页面效果

nextjs将由[]包含的文件名视为动态段,从而使得我们能够更加的运用url,我们可以通过params prop接收路由的参数,而参数本身是一种类型的承诺,它解析为一个包含动态段的对象。而组件的优点是我们可以通过async,来解析承诺并访问动态的路由段,因此导出默认异步
以下为动态路由的目录结构

嵌套动态路由

这里我们在动态路由的基础上实行嵌套路由,从而实现在展示列表产品的时候附带相关的产品信息,这里我们将创建列表产品的动态id从而实现列表评论的不同效果,这里我们先在动态路由内创建嵌套

import { notFound } from "next/navigation"
import React from "react"
export default async function ProductReview({
    params
  }: {
    params: { 
      productId: string
      reviewId: string 
    }
  }) {
    // 异步获取数据(示例)
    const reviewData = await fetchReviewData(
      params.productId,
      params.reviewId
    )
    // 模拟数据获取失败
    if(parseInt(params.productId) > 1000) {
      return notFound()
    }
    return (
      <div>
        <h1>{reviewData.productName} - 评论详情</h1>
        <h2>评论ID:{params.reviewId}</h2>
        <div>{reviewData.content}</div>
      </div>
    )
  }
  
  async function fetchReviewData(productId: string, reviewId: string) {
    // 实际开发中替换为真实API调用
    return {
      productName: `产品 ${productId}`,
      content: `评论 ${reviewId} 的详细内容...`
    }
  }

以下为代码实现的效果

这里我们再次解释以下目录的结构,以便你更好的去看待动态路由的效果

捕获所有的段

想象一下我们正在为我们的项目搭建站点,该站点拥有多个功能,同时可以满足你多个需求,同时我们做一个假设,要是我们有大量的列表需要重复实现这个功能,也就意味着我们在此基础上需要更多的路由,而这并不立于我们的开发,因此我们引用到了捕获所有的段的方式来解决这个问题。

捕获所有的段的创建方法为 [...blog],以下为目录结构

以下为实现这个效果的代码



import React from 'react';

export default async function Docs({
  params,
}: {
  params: Promise<{
    slug: string[];
  }>;
}) {
    const {slug} = await params;
    if(slug.length === 2) {
            return (
                <h1>
                    View docs for feature {slug[0]} and concept {slug[1]}
                </h1>
            );
        } else if (slug.length === 1) {
            return (
                <h1>
                    View docs for feature {slug[0]}
                </h1>
            )
            
        }
           
           return <h1>Docs home page</h1>
    }

当你在docs的不同的段长度就会出现不同的页面效果,这里我们看一下实现的效果

这里我们再次展示目录

not-found

nextjs允许我们自定义404页面,当你访问不存在的url的时候可以,将会看到基本的404页面,以及拒绝访问。我们也可以通过这种方式在出现在404之后跳转到其它的目录。

这里我们做一下这个效果,我们只需要创建一个名为not-found.jsx。从而就可以实现这个效果。这里我们还是看代码来做一个演示

export default function NotFound() {
    return(
        <div>
            <h1 style={{backgroundColor: 'red' , color: 'white',width: '100%',textAlign: 'center'}}>404 - 页面未找到</h1>
             <div style={{textAlign: 'center', marginTop: '20px',border: '1px solid #ccc', padding: '20px'}}>
                <p>抱歉,您访问的页面不存在或已删除。</p>
             </div>
         
        </div>
    )
}

这里让我们看一下404页面的演示效果

同时404页面效果也可以以编程的方式作为组件来引用,这里我们以产品的列表为例当参数超过100的时候,就会触发not-foud组件

文件共置

nextjs本身使用的文件系统的路由,因此我们可以借助这一点从而实现文件共置,例如我们可以创建line-chart.tsx从而可以实现,不必把文件放在app目录下,这里我们看一下目录结构也就是dashboard的文件下

这里是演示代码

import React from 'react';

function BarChart(){
 return <h1>my name is BarChart</h1>
}
export default function Dashboard() {
 return (
   <div>
     <h1>Dashboard</h1>
     <div>
       <h2>Line Chart <BarChart/></h2>
     </div>
 
</div>
 )
}
私人文件夹

nextjs可以处理文件安全隐患的问题,现在让我们深入了解一下,目的是告诉nextjs该文件夹不参与路由,同时你可以在其中存放一些你认为比较重要的东西
而创建的方式为下划线加问价名 _lib ,这里我们看一下目录会让你的思路更加清晰。

当你查看的时候会发现这个页面的显示效果为404

路由组

此功能可以让我们逻辑的组织我们的路由和文件从而不会影响到项目文件。
这里我们需要创建三个文件夹,login,register,forgot-password ,我们在这里展示一下目录

同时再创建一个名为auth的文件夹

并将login register forgot-password 三个文件夹放在其中,这些是实现我们路由组的一个基础,设想一下我们不可能在共同开发的时候,依旧需要不断的输入路由,因此我们才会使用路由组来进行ioc,同时将auth改为(auth)

这里我们展示一下实现效果

布局

布局是多个页面中共享的ui界面,例如我们平时所看到的页眉页脚。这里我们会讲解一下布局的创建。而实际上我们的layout.tsx本身也是一个根布局,这里我们先看一下,然后再做讲解

所以接下来我们开始讲解这段代码,以便你可以更好的理解其中的逻辑,首先这个页面是会自动保存的,当你删除之后,再次运行项目会依然出现。而children是接受子内容。children:React.ReactNode是表示允许接收任意的React组件
而底部是一个根html标签,在此我们可以添加布局,这里我们做一个代码进行测试

import React from 'react';

export const metadata = {
  title: 'Next.js',
  description: 'Generated by Next.js',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
         <header
         style={{backgroundColor: 'lightblue',
               padding: '10px',
            textAlign: 'center'}}>
        
         <p>{metadata.title}</p>
           {/* Add your header content here */}
         </header>
         {children}
          <footer style={{backgroundColor: 'lightgray',
              padding: '10px',
              textAlign: 'center'
          }}>
          <p>{metadata.description}</p>
            {/* Add your footer content here */}
          </footer>
        </body>
    </html>
  )
}

这里展示一下测试的效果

import React from 'react';

export const metadata = {
title: 'Next.js',
description: 'Generated by Next.js',
}

export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (


<header
style={{backgroundColor: 'lightblue',
padding: '10px',
textAlign: 'center'}}>

     <p>{metadata.title}</p>
       {/* Add your header content here */}
     </header>
     {children}
      <footer style={{backgroundColor: 'lightgray',
          padding: '10px',
          textAlign: 'center'
      }}>
      <p>{metadata.description}</p>
        {/* Add your footer content here */}
      </footer>
    </body>
</html>

)
}

嵌套布局

接下来我们开始讲解嵌套布局,当你想要实现一个仅在于项目的布局,这个时候就需要用到我们的嵌套布局,这个布局的效果也只会影响当前的项目。这个实现的效果也很简单,只需要你在项目中创建一个名为layout.tsx的文件。我们这里可以看一下实现的源码

  export default function ProductDetailLayout({
    children,
    }: {
    children: React.ReactNode
    }) {
    return (
        <div>
        <h1>Product Detail Layout</h1>
        {children}
        </div>
    )
} 
  

而实现的效果也跟我们预想的一样。类似于在项目中包含一个通用的ui

而这为我们目前实现效果的目录

多个根布局。

想象一下,我们要实现一个好看的页面效果,同时也要使的代码页面的整洁,因此我们就需要保证运用的路由可以同步实现在我们需要的项目中,这个时候多个根布局就为我们解决了这个问题。这里我们是将根布局移动到了auth文件夹中
我们可以先看一下目录结构

当我们这个时候要去展示页面效果就会发现我们的页面出现了基本的报错,缺少rootlayout,不过没关系,我们有办法去解决

这个时候我们只需要i将根目录下的page.tsx一同引入到auth当中,而nextjs则会自己根据路由组来进行一个查找,这里我们可以看一下再移动过page.tsx所实现的效果

路由元数据

它是一个强大的功能允许我们为每一个页面定义元数据,来确保我们的内容可以被搜索引擎共享。让我们在使用的时候感觉很棒而nextjs为我们提供了两种方法来导出静态元数据对象或导出动态的元数据对象
这里我会为你演示如何实现这个功能,我们需要在about页面下来去调用metadata函数。

这时候你会很好奇它所表现的页面效果,这里我们可以演示一下,以便你更好的理解,可以看到它并没有在页面中显示,而是需要我们打开开发者模式

相信你会很容易理解静态的元数据的效果,这里我们再尝试动态的元数据实现,我们所用到的页面是products,这里我们还是看一下目录以便我们更好的去实现动态效果

接下来我会为你演示所需要的代码,对于需要用到metadata的动态方法,就需要我们在顶部引入组件,这里的代码结构会需要你花一些时间去理解,不过没关系,我们所要实现的功能是当你在切换products/{id}的时候。
所标题也会同样的改变

// src/app/products/[productId]/page.tsx
import type { Metadata } from "next";

interface Props {
  params: { productId: string }; // 修复类型定义
}

// 元数据生成函数
export async function generateMetadata({ params }: Props): Promise<Metadata> {
  return {
    title: `Product ${params.productId}`, // 修复模板字符串和参数引用
    description: `Details for product ${params.productId}`
  };
}

// 动态路由页面组件
export default async function ProductDetails({ params }: Props) {
  return (
    
    <div className="p-4">
      <h1 className="text-2xl font-bold">
        Details about product {params.productId} {/* 修复变量引用 */}
      </h1>
    </div>
  );
}

标题元数据

在路由标题字段起到非常重要的作用,而它的主要作用是定义文档标题,你可以用字符串,或者是对象来设置它。这里我们利用代码来做一个演示,当我们设置来标题的元数据,可以在浏览器中发现标题已经可以发生变化

同样的我想你一定会觉得这样的调用在多个页面的前提会让你觉得繁琐,因此我们这里会再使用动态的路由元数据,我想这样对于你处理大量的页面也会是一种帮助。这里我们需要在auth中来对路由进行一个代码上的改动,以及引入Metadata函数

同样的,我们将为你准备好了源码,

 
import { Metadata } from "next";

// import { Inter } from "next/font/google"j
export const metadata: Metadata = {
title: {
 default: "Next.js Tutorial-Codevolution",
 template: "%s | Codevolution",
 absolute: "",
},
description: "Generated by Codevolution",
}

export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (

  <html lang="en">
    <body>{children}</body>
  </html>
)
}

import { Metadata } from "next";

// import { Inter } from "next/font/google"j
export const metadata: Metadata = {
title: {
 default: "Next.js Tutorial-Codevolution",
 template: "%s | Codevolution",
 absolute: "",
},
description: "Generated by Codevolution",
}

export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (

  <html lang="en">
    <body>{children}</body>
  </html>
)
}

当你在路由组的文件中可以发现,你的标题元数据,已经可以动态的发生改变,同样的。我们这里看一下演示效果

链接组建

路由系统为我们介绍了该如何去设置路由规则,嵌套路由,路由组,动态路由,以及如何去利用路由来进行页面的合理调控。这里没没有让用户感觉到路由的一些定向,因此这里我们需要利用一个组建,当用户点击触发的时候,可以进行页面的跳转,而链接的组建本身是基于HTML的一种跳转。这里我们需要用的页面为products.

而实现的代码,也会为你准备在下方,这里链接的组件为,我们可以看到在盒子中,我们对于列表引用了Link的元素 ,同时我们也定了,一个动态的productid

import Link from 'next/link';
import React from 'react';

export default function ProductList(){
    const productId=100;
    return (
    
        <div>
            <Link href="/">Home</Link>
       <h1>Product List</h1>
       <h2> 
        <Link href="/products/1">Product 1</Link>
        </h2>
        <h2>
        <Link href="/products/2">Product 2</Link>
        </h2>
         <h2>
            <Link href="/products/3">Product 3</Link>
         </h2>
         <h2>
            <Link href={`/products/${productId}`}>Product {productId}</Link>
            </h2>
        </div>
    )
}


####活动链接
   我们构建活动链接不仅是为了美化我们的页面,同时也让我们的链接来更加的实用,它可以帮助用户了解其中的位置,并且也为我们节省了时间。这里我创建了一个布局,同时包含三个活动的链接,而选择的文件为根目录下的layout.tsx文件 ,当选中链接的时候,链接周围出现蓝色部分区域,

```jsx
"use client"
import React from 'react'
import Link from 'next/link'
import { usePathname } from 'next/navigation'

const navLinks = [
  { name: 'Home', href: '/' },
  { name: 'About', href: '/about' },
  { name: 'Contact', href: '/contact' },
]

export default function DashboardLayout({
    children,
  }: {
    children: React.ReactNode
  }) {
    const pathname =usePathname();
    return (

      <html lang="en">
        <body>
          {/* Layout UI */}
          {/* Place children where you want to render a page or nested layout */}
          <main>{children}</main>
        </body>
        <div>
          {navLinks.map((link) => {
            const isActive = 
              pathname === link.href ||
              (pathname.startsWith(link.href) && link.href !== '/');
            return (
              <Link
                className={isActive ? 'font-bold mr-4' : ' text-blue-500 mr-4'}
                key={link.name}
                href={link.href}
              >
                {link.name}
              </Link>
            );
          })}
        </div>
      </html>
      
    )
  }
```
这里为了更好的看清我们的样式效果,可以再创建一个styles.css的样式文件,并在layout.tsx中导入
![](https://img2024.cnblogs.com/blog/3632196/202505/3632196-20250511151803003-705938830.png)

#####深入学习params and searchparams
  如果你现在正在使用导航一定需要理解这两个概念,简单理解为params是一个解析一个包含动态链接路由参数的一个对象,而searchparams是一个包含查询解析动态路由参数的一个对象,同时我们可以将参数传递给浏览器。
````jsx
   
import Link from 'next/link';
import React from 'react';
export default async function NewsArticle({
    params,
    searchParams,
}: {
    params: Promise<{ articleId: string }>;
    searchParams: Promise<{ lang?: "en" | "es" | "cn" }>;
}) {
    const { articleId } = await params;
    const { lang = "en" } = await searchParams;

return (
<div>
    
     <h1>News article id</h1>
     <p>This is the content of the news article</p>

     <div>
        <Link href={`/articles/${articleId}?lang=${lang}`}>Current Language</Link>
        <Link href={`/articles/${articleId}?lang=en`}>English</Link>
        <Link href={`/articles/${articleId}?lang=es`}>Spanish</Link>
        <Link href={`/articles/${articleId}?lang=cn`}>ZhongWen</Link>
     </div>
 </div>
)
}
```
####以编程式导航
 当你在完成一次订单的时候,页面将会重定向到对应的物品信息页面,程序化本身对实现相应的功能至关重要。这里我们将通过nextjs实现对应的功能,作为案例我们会尽可能简单一些。这里我们在app目录下创建order-product文件![](https://img2024.cnblogs.com/blog/3632196/202505/3632196-20250511160159494-1653781319.png)
 同时我们讲解一下代码。这里我们定义了一个点击的事件,在按钮上,同时在我们点击的时候就会触发console.log 以及触发定义的路由。
```jsx
"use client"
import { useRouter } from 'next/navigation';

export default function  OrderProduct(){
  
    const router = useRouter();
    const handleClick = () => {

        console.log('placing your order');
        router.push('/');
    }
    return (
        <div>"use client"
import { useRouter } from 'next/navigation';

export default function  OrderProduct(){
  
    const router = useRouter();
    const handleClick = () => {

        console.log('placing your order');
        router.push('/');
    }
    return (
        <div>
            <h1>Order Product</h1>
            <p>This is the order product page</p>
            <button onClick={handleClick}>pleasr order</button>
        </div>
    )
}
            <h1>Order Product</h1>
            <p>This is the order product page</p>
            <button onClick={handleClick}>pleasr order</button>
        </div>
    )
}
```
####模板
模板类似于ui布局,它同样可以满足多个页面具有同样的效果,而主要的区别在于,每当用户实现共享模板之间的导航时,你都会得到一个全新的dom元素,这里我们可以在路由组中实现挂载。功能的是实现是要求我们在路由组中,同时引用**import { useState } from 'react';**,这里我们再定义一个方法const [input,setInput] =useState("");
 这里我们来看一下样式效果,同样的当你在路由组的根路由下实现这个模板,会进行一个虚拟dom的实现
![](https://img2024.cnblogs.com/blog/3632196/202505/3632196-20250511165156163-2014195964.png)

####ui
 创建加载状态,用户在特定的路由情况下可以看到加载的状态,让用户知道应用程序正在积极加载,而加载的功能同样简单,就如同我们当初创建not-found页面,同时使用加载也存在两个好处,一方面当用户导航到一个新地方时,会立刻给用户反馈,让用户知道页面正在积极响应,其次nextjs保持页面的共享,当主车程序没有加载好依然可以使用侧边栏。这里我们同样以代码作为演示,以便更好的感受到ui效果这里我们创建一个loading.tsx文件
![](https://img2024.cnblogs.com/blog/3632196/202505/3632196-20250512091101757-366804622.png)
```jsx
import {Metadata} from 'next';

export const metadata = {
        title:{
        absolute:"Blog",

},
};

export default async function Blog() {
      // Simulate slow loading
      await new Promise((resolve) =>
       {
    setTimeout(() => { 
        resolve("Loading complete");

      },2000);
    });
    return <div>Mb blog</div>;
}
```

####错误处理
 当我们遇到一个错误时,是无法再次运行项目的。同时我们可以通过错误处理,让其只影响部分程序路由。这里我们需要在路由文件下创建一个error.jsx文件并默认导出为客户端,而这里我们将用图片来作为一个演示
![](https://img2024.cnblogs.com/blog/3632196/202505/3632196-20250512093834721-730441127.png)
error是一个很强大的功能,它能够将有错误的路由包含在特殊段中,不会因为这一个报错,从而导致项目无法运行
这里我们可以看一些文件目录的结构,以及对应的引用ui nextjs是一个轻量级的文件系统,因此需要你更好的去理解我们在创建页面时的一些数据结构,这也会帮助你更好的理解
![](https://img2024.cnblogs.com/blog/3632196/202505/3632196-20250512094323760-383687593.png)

####从错误中恢复
  我们会发现有的错误看起来会有一些严重而实际上,它也可以通过一些手段去处理,除了错误支撑之外从此类错误中恢复,错误中的错误边界,tsx为我们提供了另一个有帮助的函数**prop**,这里我将会为你演示,我们基于的文件是err.tsx,这里让我们看一下目录,为了让你更好的知道![](https://img2024.cnblogs.com/blog/3632196/202505/3632196-20250513085539843-267089771.png)
```jsx
"use client";

import { useRouter } from "next/router";
import { startTransition } from "react";


export default function ErrorBoundary({
    error,
    reset,
}:{
    error: Error,
    reset: () => void}) {
    const router = useRouter();
    const reload = () => {
        startTransition(() => {
            router.replace(router.asPath);
            reset();
        });
    };

    return (

        <div>
  
          <p>{error.message}</p>
          <button onClick={reload}>Try again</button>
    </div>

    )
}
```
  这里我们在一开始的只是回调页面转化为处理服务器端出现报错,同时继续渲染,对于恢复我们需要从react开始转化并使用来之下一个的共享路由,下面是实现从一个导航中使用路由器。路由器通过const Router来定义一个路由器常量,接下来我们再定义一个包含路由参数的reload,同时在里面定义一个更新的方法,如果你想独立去测试这个功能可以去结合嵌套路由。我想你可能会不太理解这段功能,实际上,这个功能就类似于页面的刷新效果。

####处理嵌套路由中的错误
错误总会冒泡以找到最近的父错误边界,这也就意味着不仅可以处理自身的错误,还可以通过策略性的放置错误来处理其下方的所有嵌套子段的错误。当我们的error.tsx文件夹通过移动位置,就可以处理错误的详细程度

####处理布局中的错误
 tsx的错误会处理所有嵌套路由的错误,但布局只有一个,边界的tsx组件不会捕获布局中的错误,同样的解决问题的方法也很多,只需要将error.tsx文件移动到段的父类文件下。
####处理全局中的错误
你可能会想知道,边界无法捕获布局中的错误来自同一段的tsx文件,而同样的根布局的错误该如何处理,没有父段,我们该如何去处理这种错误。我们同样考虑到了这一点,所以提供了一个全局中的错误处理文件,位于根目录,在根目录下创建error-wrapper.tsx
    目前这里是基础的nextjsi知识,为了防止页面太过于凌乱,接下来我会将一些进阶的知识创建为nextjs进阶知识。同时也希望这些知识能够帮助你更好的理解这门语言。为了让下面的错误案例更加的具体,稍后我会补充上相关案例的代码。
posted @ 2025-04-13 22:41  白鹿为溪  阅读(51)  评论(0)    收藏  举报