Next.js 14 路由进阶:从约定式到动态路由的最佳实践

在 Next.js 14 中,路由系统是最核心的功能之一。App Router 不仅带来了更好的性能,还提供了更灵活的路由组织方式。今天,我们就来深入探讨 Next.js 14 的路由系统。

路由组织结构

1. 基础约定

Next.js 14 的路由基于文件系统,每个文件夹代表一个路由段:

ini 复制代码
app/
├── page.tsx           # 首页 (/)
├── about/
│   └── page.tsx      # 关于页面 (/about)
├── blog/
│   ├── page.tsx      # 博客列表页 (/blog)
│   └── [slug]/       # 动态路由
│       └── page.tsx  # 博客详情页 (/blog/post-1)
└── (marketing)/      # 路由组
    ├── pricing/
    │   └── page.tsx  # 价格页面 (/pricing)
    └── contact/
        └── page.tsx  # 联系页面 (/contact)

2. 路由组织最佳实践

typescript 复制代码
// 使用路由组进行功能分组
app/
├── (auth)/              # 认证相关路由
│   ├── login/
│   │   └── page.tsx
│   └── register/
       └── page.tsx
├── (dashboard)/         # 仪表板相关路由
│   ├── layout.tsx      # 仪表板共享布局
│   ├── overview/
│   │   └── page.tsx
│   └── settings/
│       └── page.tsx
└── (public)/           # 公开页面
    ├── layout.tsx
    └── page.tsx

动态路由设计

1. 单一动态段

typescript 复制代码
// app/users/[id]/page.tsx
export default async function UserProfile({ 
  params 
}: { 
  params: { id: string } 
}) {
  const user = await fetchUser(params.id);
  
  return (
    <div className="user-profile">
      <h1>{user.name}</h1>
      <div className="user-details">
        {/* 用户详情 */}
      </div>
    </div>
  );
}

// 生成静态路由参数
export async function generateStaticParams() {
  const users = await fetchUsers();
  
  return users.map((user) => ({
    id: user.id.toString(),
  }));
}

2. 多段动态路由

typescript 复制代码
// app/[category]/[subCategory]/[productId]/page.tsx
interface ProductPageProps {
  params: {
    category: string;
    subCategory: string;
    productId: string;
  };
}

export default async function ProductPage({ params }: ProductPageProps) {
  const { category, subCategory, productId } = params;
  
  const product = await fetchProduct({
    category,
    subCategory,
    productId,
  });
  
  return (
    <div className="product-page">
      <Breadcrumb
        items={[category, subCategory, product.name]}
      />
      <ProductDetails product={product} />
    </div>
  );
}

3. 捕获所有路由

typescript 复制代码
// app/docs/[...slug]/page.tsx
export default async function DocPage({ 
  params 
}: { 
  params: { slug: string[] } 
}) {
  // slug 是一个数组,包含所有路径段
  const path = params.slug.join('/');
  const doc = await fetchDoc(path);
  
  return (
    <div className="doc-content">
      <DocRenderer content={doc.content} />
    </div>
  );
}

路由拦截与中间件

1. 路由中间件

typescript 复制代码
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // 获取当前路径
  const path = request.nextUrl.pathname;
  
  // 检查认证状态
  const isAuthenticated = request.cookies.has('auth-token');
  
  // 保护的路由
  if (path.startsWith('/dashboard') && !isAuthenticated) {
    return NextResponse.redirect(new URL('/login', request.url));
  }
  
  // 添加自定义请求头
  const response = NextResponse.next();
  response.headers.set('x-custom-header', 'my-value');
  
  return response;
}

// 配置中间件匹配路径
export const config = {
  matcher: [
    '/dashboard/:path*',
    '/api/:path*',
  ],
};

2. 路由拦截

typescript 复制代码
// app/posts/[id]/page.tsx
import { headers } from 'next/headers';

export default async function Post({ params }: { params: { id: string } }) {
  const headersList = headers();
  const referer = headersList.get('referer');
  
  // 检查是否从允许的来源访问
  if (referer && !isAllowedReferer(referer)) {
    redirect('/unauthorized');
  }
  
  const post = await fetchPost(params.id);
  return <PostContent post={post} />;
}

平行路由和拦截路由

1. 平行路由

typescript 复制代码
// app/layout.tsx
export default function Layout({
  children,
  auth,
  modal,
}: {
  children: React.ReactNode;
  auth: React.ReactNode;
  modal: React.ReactNode;
}) {
  return (
    <div className="layout">
      {children}
      {auth}
      {modal}
    </div>
  );
}

// app/@auth/login/page.tsx
export default function LoginModal() {
  return (
    <div className="modal">
      <LoginForm />
    </div>
  );
}

// app/@modal/photo/[id]/page.tsx
export default function PhotoModal({
  params,
}: {
  params: { id: string };
}) {
  return (
    <div className="modal">
      <PhotoViewer id={params.id} />
    </div>
  );
}

2. 路由拦截模式

typescript 复制代码
// app/feed/page.tsx
import { Feed } from '@/components/Feed';

export default function FeedPage() {
  return <Feed />;
}

// app/feed/(..)photo/[id]/page.tsx
export default function InterceptedPhotoModal({
  params,
}: {
  params: { id: string };
}) {
  return (
    <div className="modal">
      <PhotoViewer id={params.id} />
    </div>
  );
}

路由加载状态

1. 加载界面

typescript 复制代码
// app/posts/loading.tsx
export default function Loading() {
  return (
    <div className="loading-container">
      <div className="loading-skeleton">
        <div className="skeleton-header" />
        <div className="skeleton-content" />
      </div>
    </div>
  );
}

2. 流式渲染

typescript 复制代码
// app/dashboard/page.tsx
import { Suspense } from 'react';

export default function Dashboard() {
  return (
    <div className="dashboard">
      <Suspense fallback={<StatsSkeleton />}>
        <Stats />
      </Suspense>
      
      <Suspense fallback={<ChartsSkeleton />}>
        <Charts />
      </Suspense>
      
      <Suspense fallback={<TableSkeleton />}>
        <Table />
      </Suspense>
    </div>
  );
}

路由缓存策略

1. 路由段缓存

typescript 复制代码
// app/products/[id]/page.tsx
export const revalidate = 3600; // 1小时后重新验证

export default async function ProductPage({
  params,
}: {
  params: { id: string };
}) {
  const product = await fetchProduct(params.id);
  
  return (
    <div className="product">
      <h1>{product.name}</h1>
      <ProductDetails product={product} />
    </div>
  );
}

2. 按需重新验证

typescript 复制代码
// app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from 'next/cache';
import { NextRequest } from 'next/server';

export async function POST(request: NextRequest) {
  const { path, tag } = await request.json();
  
  if (path) {
    revalidatePath(path);
  }
  
  if (tag) {
    revalidateTag(tag);
  }
  
  return Response.json({ revalidated: true });
}

高级路由技巧

1. 条件路由

typescript 复制代码
// app/[lang]/layout.tsx
export default async function LocaleLayout({
  children,
  params: { lang },
}: {
  children: React.ReactNode;
  params: { lang: string };
}) {
  const messages = await loadMessages(lang);
  
  return (
    <html lang={lang}>
      <body>
        <LocaleProvider messages={messages}>
          {children}
        </LocaleProvider>
      </body>
    </html>
  );
}

// 生成静态路由参数
export async function generateStaticParams() {
  return [
    { lang: 'en' },
    { lang: 'zh' },
    { lang: 'ja' },
  ];
}

2. 路由组合

typescript 复制代码
// app/(shop)/products/[category]/layout.tsx
export default function ShopLayout({
  children,
  params,
}: {
  children: React.ReactNode;
  params: { category: string };
}) {
  return (
    <div className="shop-layout">
      <aside>
        <CategoryNav category={params.category} />
      </aside>
      <main>{children}</main>
    </div>
  );
}

性能优化建议

  1. 路由预加载
typescript 复制代码
import { useRouter } from 'next/navigation';

function NavLink({ href, children }) {
  const router = useRouter();
  
  return (
    <a
      href={href}
      onMouseEnter={() => router.prefetch(href)}
    >
      {children}
    </a>
  );
}
  1. 选择性缓存
typescript 复制代码
async function getData() {
  const res = await fetch('https://api.example.com/data', {
    next: {
      tags: ['data'],      // 使用标签进行缓存
      revalidate: 3600,    // 缓存时间
    },
  });
  
  return res.json();
}

写在最后

Next.js 14 的路由系统提供了强大的功能和灵活的配置选项。合理使用这些特性,可以构建出性能优秀、用户体验良好的应用。记住以下几点:

  1. 合理组织路由结构,使用路由组进行功能分组
  2. 善用动态路由和路由拦截
  3. 适当配置缓存策略
  4. 注意性能优化
  5. 使用中间件增强路由功能

在下一篇文章中,我们将深入探讨 Next.js 14 的数据获取和状态管理。如果你有任何问题或建议,欢迎在评论区讨论!

如果觉得这篇文章对你有帮助,别忘了点个赞 👍

相关推荐
真的很上进10 小时前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html
wakangda21 小时前
React Native 集成原生Android功能
javascript·react native·react.js
秃头女孩y1 天前
【React中最优雅的异步请求】
javascript·vue.js·react.js
前端小小王1 天前
React Hooks
前端·javascript·react.js
迷途小码农零零发1 天前
react中使用ResizeObserver来观察元素的size变化
前端·javascript·react.js
不是鱼2 天前
构建React基础及理解与Vue的区别
前端·vue.js·react.js
飞翔的渴望2 天前
antd3升级antd5总结
前端·react.js·ant design
╰つ゛木槿2 天前
深入了解 React:从入门到高级应用
前端·react.js·前端框架
用户30587584891252 天前
Connected-react-router核心思路实现
react.js