Nextjs初探 nextjs first Nextjs 前端
1402 字
7 分钟
Nextjs初探
2025-02-14

Nextjs#

Nextjs现在成为react官方推荐的第一框架,虽然偏向的是全栈框架,但是其性价比非常高,一个框架可以直接做出一个完整的项目,是个人开发者的不二之选

Learn#

Next.js 的 App Router(从 v13 开始引入)是一种基于文件系统的新型路由方案,结合 TypeScript (.tsx) 使用能提供更好的类型安全,同时我比较推荐从Nextjs Learn开始入门


1. 项目初始化#

使用 TypeScript 模板创建项目:

Terminal window
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. 实用技巧#

  1. 路由跳转

    import Link from 'next/link';
    <Link href="/blog/123">Read Blog</Link>
  2. 流式传输

    import { Suspense } from 'react';
    <Suspense fallback={<div>Loading...</div>}>
    <AsyncComponent />
    </Suspense>
  3. 环境变量

    const apiUrl = process.env.NEXT_PUBLIC_API_URL;

常见问题#

  • 类型错误:确保为 paramssearchParams 定义正确的接口。
  • 客户端组件限制:在客户端组件中不能直接使用服务端数据获取方法(如 getStaticProps)。
  • 动态导入:使用 next/dynamic 导入客户端组件。

官方资源#

next-auth#

在 Next.js 中使用 next-auth 实现鉴权的完整指南(支持 App Router 和 TypeScript):


步骤 1:安装依赖#

Terminal window
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>
);
}

关键配置说明#

  1. 适配器选择

    • 使用 PrismaAdapter 将用户数据存入数据库
    • 支持其他适配器:Firebase、MongoDB 等
  2. OAuth 回调 URL

    • 在 GitHub 开发者设置中添加回调 URL: http://localhost:3000/api/auth/callback/github
  3. Session 策略

    session: {
    strategy: "jwt", // 或 "database"
    }

常见问题解决#

  1. 类型错误

    next-auth.d.ts
    import "next-auth";
    declare module "next-auth" {
    interface Session {
    user: {
    id: string;
    } & DefaultSession["user"];
    }
    }
  2. 中间件不生效

    • 确保 matcher 路径正确
    • 检查中间件文件是否在项目根目录
  3. 生产环境配置

    • 设置 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 字段,通过中间件校验。


文档参考:NextAuth.js Documentation

解决国内网络登录显示不成功的问题#

Nextjs初探
https://bangwu.top/posts/nextjs-first/
作者
棒无
发布于
2025-02-14
许可协议
CC BY-NC-SA 4.0