夏日幽灵 サマーゴースト (2021)
1402 字
7 分钟
Nextjs初探
Nextjs
Nextjs现在成为react官方推荐的第一框架,虽然偏向的是全栈框架,但是其性价比非常高,一个框架可以直接做出一个完整的项目,是个人开发者的不二之选
Learn
Next.js 的 App Router(从 v13 开始引入)是一种基于文件系统的新型路由方案,结合 TypeScript (.tsx
) 使用能提供更好的类型安全,同时我比较推荐从Nextjs Learn开始入门
1. 项目初始化
使用 TypeScript 模板创建项目:
npx create-next-app@latest --ts
选择启用 App Router(默认选项)。
2. App Router 核心结构
项目目录结构:
app/
├── layout.tsx # 全局布局
├── page.tsx # 首页(对应路由 `/`)
├── dashboard/
│ ├── layout.tsx # 嵌套布局
│ └── page.tsx # `/dashboard`
└── blog/
├── [slug]/
│ └── page.tsx # 动态路由 `/blog/:slug`
└── page.tsx # `/blog`
3. 基础页面与布局
全局布局 (app/layout.tsx
)
import type { ReactNode } from 'react';
export default function RootLayout({
children,
}: {
children: ReactNode;
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
首页 (app/page.tsx
)
export default function Home() {
return <h1>Hello, Next.js!</h1>;
}
4. 动态路由与 TypeScript
动态路由示例 (app/blog/[slug]/page.tsx
)
interface PageProps {
params: { slug: string };
searchParams?: { [key: string]: string | string[] | undefined };
}
export default function BlogPage({ params }: PageProps) {
return <div>Blog Slug: {params.slug}</div>;
}
生成动态路由参数 (generateStaticParams
)
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts').then((res) => res.json());
return posts.map((post: { id: string }) => ({
slug: post.id,
}));
}
5. 数据获取与类型安全
服务端数据获取 (fetch
+ TypeScript)
interface Post {
id: string;
title: string;
content: string;
}
async function getPost(slug: string): Promise<Post> {
const res = await fetch(`https://api.example.com/posts/${slug}`);
if (!res.ok) throw new Error('Failed to fetch');
return res.json();
}
export default async function BlogPage({ params }: PageProps) {
const post = await getPost(params.slug);
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
6. 嵌套布局与类型
嵌套布局 (app/dashboard/layout.tsx
)
import type { ReactNode } from 'react';
export default function DashboardLayout({
children,
}: {
children: ReactNode;
}) {
return (
<div>
<nav>Dashboard Navbar</nav>
{children}
</div>
);
}
7. 客户端组件与交互
客户端组件 (app/components/Counter.tsx
)
'use client'; // 必须标记为客户端组件
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>+</button>
<span>{count}</span>
</div>
);
}
8. API 路由 (可选)
创建 API 路由 (app/api/hello/route.ts
):
import { NextResponse } from 'next/server';
export async function GET() {
return NextResponse.json({ name: 'John Doe' });
}
访问 /api/hello
即可调用。
9. 类型增强配置
在 tsconfig.json
中添加 Next.js 类型:
{
"compilerOptions": {
"types": ["next", "next/types"]
}
}
10. 实用技巧
路由跳转:
import Link from 'next/link'; <Link href="/blog/123">Read Blog</Link>
流式传输:
import { Suspense } from 'react'; <Suspense fallback={<div>Loading...</div>}> <AsyncComponent /> </Suspense>
环境变量:
const apiUrl = process.env.NEXT_PUBLIC_API_URL;
常见问题
- 类型错误:确保为
params
和searchParams
定义正确的接口。 - 客户端组件限制:在客户端组件中不能直接使用服务端数据获取方法(如
getStaticProps
)。 - 动态导入:使用
next/dynamic
导入客户端组件。
官方资源
- Next.js App Router 文档:App Router Docs
- TypeScript 配置指南:TypeScript Guide
next-auth
在 Next.js 中使用 next-auth
实现鉴权的完整指南(支持 App Router 和 TypeScript):
步骤 1:安装依赖
pnpm install next-auth @next-auth/prisma-adapter @prisma/client bcrypt
步骤 2:配置环境变量
# .env
NEXTAUTH_SECRET="your-secure-secret" # openssl rand -base64 32 生成
NEXTAUTH_URL="http://localhost:3000"
# GitHub OAuth 示例
GITHUB_CLIENT_ID="your-client-id"
GITHUB_CLIENT_SECRET="your-client-secret"
# 数据库配置(以 PostgreSQL 为例)
DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
步骤 3:创建 Prisma Schema(数据库集成)
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
}
model Account {
id String @id @default(cuid())
userId String
type String
provider String
providerAccountId String
refresh_token String? @db.Text
access_token String? @db.Text
expires_at Int?
token_type String?
scope String?
id_token String? @db.Text
session_state String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
}
model Session {
id String @id @default(cuid())
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
运行 npx prisma generate
生成客户端。
步骤 4:配置 NextAuth (App Router)
// app/api/auth/[...nextauth]/route.ts
import NextAuth from "next-auth";
import GitHubProvider from "next-auth/providers/github";
import { PrismaAdapter } from "@next-auth/prisma-adapter";
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
export const {
handlers: { GET, POST },
auth, // 用于中间件获取会话
signIn,
signOut,
} = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [
GitHubProvider({
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
}),
],
secret: process.env.NEXTAUTH_SECRET,
});
步骤 5:全局 SessionProvider
// app/layout.tsx
import type { ReactNode } from "react";
import { SessionProvider } from "next-auth/react";
import { auth } from "@/auth";
export default async function RootLayout({
children,
}: {
children: ReactNode;
}) {
const session = await auth();
return (
<html lang="en">
<body>
<SessionProvider session={session}>{children}</SessionProvider>
</body>
</html>
);
}
步骤 6:创建登录/登出组件
// components/AuthButton.tsx
"use client";
import { signIn, signOut } from "@/auth";
import { useSession } from "next-auth/react";
export function AuthButton() {
const { data: session } = useSession();
return (
<div>
{session ? (
<button onClick={() => signOut()}>Sign Out</button>
) : (
<button onClick={() => signIn("github")}>Sign In with GitHub</button>
)}
</div>
);
}
步骤 7:保护路由(中间件)
// middleware.ts
import { auth } from "@/auth";
export default auth((req) => {
if (!req.auth) {
return Response.redirect(new URL("/login", req.nextUrl));
}
});
// 配置需要保护的路径
export const config = {
matcher: ["/dashboard/:path*", "/profile"],
};
步骤 8:服务端获取会话
// app/dashboard/page.tsx
import { auth } from "@/auth";
export default async function DashboardPage() {
const session = await auth();
if (!session) {
return <div>Unauthorized</div>;
}
return <div>Welcome {session.user?.name}</div>;
}
步骤 9:自定义登录页面
// app/login/page.tsx
"use client";
import { signIn } from "@/auth";
export default function LoginPage() {
return (
<div>
<button onClick={() => signIn("github")}>GitHub 登录</button>
</div>
);
}
关键配置说明
适配器选择:
- 使用
PrismaAdapter
将用户数据存入数据库 - 支持其他适配器:Firebase、MongoDB 等
- 使用
OAuth 回调 URL:
- 在 GitHub 开发者设置中添加回调 URL:
http://localhost:3000/api/auth/callback/github
- 在 GitHub 开发者设置中添加回调 URL:
Session 策略:
session: { strategy: "jwt", // 或 "database" }
常见问题解决
类型错误:
// next-auth.d.ts import "next-auth"; declare module "next-auth" { interface Session { user: { id: string; } & DefaultSession["user"]; } }
中间件不生效:
- 确保
matcher
路径正确 - 检查中间件文件是否在项目根目录
- 确保
生产环境配置:
- 设置
NEXTAUTH_URL
为生产域名 - 使用 HTTPS
- 设置
扩展功能
邮箱登录:
import EmailProvider from "next-auth/providers/email"; providers: [ EmailProvider({ server: process.env.EMAIL_SERVER, from: process.env.EMAIL_FROM, }), ]
自定义认证页面:
theme: { logo: "/logo.png", brandColor: "#000", }
角色鉴权: 在
User
模型中添加role
字段,通过中间件校验。