框架

Next.js

App Router、导航、数据获取与常用配置片段。 — 共 19 条

共 19 条代码片段

A. App Router

页面 metadata

export const metadata = {
  title: "页面标题",
  description: "页面描述",
};

动态 generateMetadata

type Props = {
  params: Promise<{ slug: string }>;
};

export async function generateMetadata({ params }: Props) {
  const { slug } = await params;
  return {
    title: slug,
    description: `关于 ${slug} 的页面`,
  };
}

动态路由 params

type Props = {
  params: Promise<{ slug: string }>;
};

export default async function Page({ params }: Props) {
  const { slug } = await params;
  return <div>{slug}</div>;
}

searchParams

type Props = {
  searchParams: Promise<{ q?: string; page?: string }>;
};

export default async function Page({ searchParams }: Props) {
  const { q = "", page = "1" } = await searchParams;
  return (
    <div>
      关键词:{q} / 页码:{page}
    </div>
  );
}

generateStaticParams

export function generateStaticParams() {
  return [{ slug: "a" }, { slug: "b" }, { slug: "c" }];
}

notFound

import { notFound } from "next/navigation";

export default async function Page({ params }: Props) {
  const data = await getData((await params).id);
  if (!data) notFound();
  return <div>{data.title}</div>;
}

redirect / permanentRedirect

import { redirect, permanentRedirect } from "next/navigation";

redirect("/login");
permanentRedirect("/new-path");

loading.tsx / error.tsx

// app/blog/loading.tsx
export default function Loading() {
  return <div>加载中…</div>;
}
// app/blog/error.tsx
"use client";

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

B. 组件与导航

客户端组件

"use client";

import { useState } from "react";

export default function Counter() {
  const [n, setN] = useState(0);
  return <button onClick={() => setN(n + 1)}>{n}</button>;
}

useRouter

"use client";

import { useRouter } from "next/navigation";

const router = useRouter();
router.push("/dashboard");
router.replace("/login");
router.back();
router.refresh();

usePathname / useSearchParams

"use client";

import { usePathname, useSearchParams } from "next/navigation";

const pathname = usePathname();
const searchParams = useSearchParams();
const q = searchParams.get("q");

next/image

import Image from "next/image";

<Image
  src="/cover.jpg"
  alt="封面"
  width={800}
  height={450}
  priority
/>

C. 数据与接口

服务端 fetch

const res = await fetch("https://api.example.com/posts", {
  next: { revalidate: 60 }, // ISR
});
const data = await res.json();

缓存策略

// 强制缓存
await fetch(url, { cache: "force-cache" });

// 不缓存
await fetch(url, { cache: "no-store" });

Route Handler

// app/api/hello/route.ts
import { NextResponse } from "next/server";

export async function GET() {
  return NextResponse.json({ ok: true });
}

export async function POST(request: Request) {
  const body = await request.json();
  return NextResponse.json({ received: body });
}

revalidatePath / revalidateTag

import { revalidatePath, revalidateTag } from "next/cache";

revalidatePath("/blog");
revalidateTag("posts");

D. 配置

next.config.ts 常用项

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  images: {
    remotePatterns: [
      { protocol: "https", hostname: "cdn.example.com" },
    ],
  },
  async redirects() {
    return [{ source: "/old", destination: "/new", permanent: true }];
  },
};

export default nextConfig;

环境变量

# .env.local
NEXT_PUBLIC_API_HOST=https://api.example.com
API_SECRET=xxx

# 客户端可读:NEXT_PUBLIC_*
process.env.NEXT_PUBLIC_API_HOST

# 仅服务端
process.env.API_SECRET