1402 字
7 分钟
Nextjs初探
2025-02-14

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

  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:安装依赖#

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