0Pricing
Next.js 15 Fullstack (App Router + Server Actions) · 课时

构建全栈应用

完成一个综合项目,将所学概念整合到一个完整的全栈 Next.js 应用中。

构建全栈应用 是 CoddyKit 上的免费 Next.js 15 Fullstack (App Router + Server Actions) 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Next.js 15 Fullstack (App Router + Server Actions) 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Next.js 15 Fullstack (App Router + Server Actions) 课程共包含 4 节课。

本课时的部分内容尚未翻译,以英文显示。

Fullstack App: Task Manager

Welcome to building a real-world fullstack application with Next.js 15! In this lesson, we'll integrate all the concepts you've learned to create a simple task manager.

Our app will allow users to:

  • Sign in securely.
  • View their personal tasks.
  • Add new tasks.
  • Mark tasks as complete.

This will demonstrate how Server Components, Client Components, Server Actions, data fetching, and authentication work together.

Project Init & Database Setup

First, let's set up our project and define the database schema using Prisma. We'll need models for users and tasks.

  • Initialize Next.js: npx create-next-app@latest my-fullstack-app
  • Install Prisma: npm install prisma @prisma/client
  • Initialize Prisma: npx prisma init --datasource-provider sqlite (or your preferred DB)

Here's a basic schema.prisma for our task manager:

// prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "sqlite" // or "postgresql", "mysql"
  url      = env("DATABASE_URL")
}

model User {
  id        String    @id @default(cuid())
  email     String    @unique
  name      String?
  password  String // Hashed password
  tasks     Task[]
}

model Task {
  id        String    @id @default(cuid())
  title     String
  completed Boolean   @default(false)
  userId    String
  user      User      @relation(fields: [userId], references: [id])
  createdAt DateTime  @default(now())
  updatedAt DateTime  @updatedAt
}

Implementing NextAuth.js

Authentication is a core part of any fullstack application. We'll integrate NextAuth.js to handle user sign-in, sign-up, and session management. This involves creating an API route for NextAuth.

Our [...nextauth] route defines how users authenticate:

// app/api/auth/[...nextauth]/route.ts
import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import { PrismaAdapter } from "@auth/prisma-adapter";
import { PrismaClient } from "@prisma/client";
import bcrypt from "bcrypt";

const prisma = new PrismaClient();

const handler = NextAuth({
  adapter: PrismaAdapter(prisma),
  providers: [
    CredentialsProvider({
      name: "Credentials",
      credentials: {
        email: { label: "Email", type: "text" },
        password: { label: "Password", type: "password" }
      },
      async authorize(credentials) {
        if (!credentials?.email || !credentials?.password) return null;

        const user = await prisma.user.findUnique({
          where: { email: credentials.email }
        });

        if (!user || !(await bcrypt.compare(credentials.password, user.password))) {
          return null;
        }
        return { id: user.id, email: user.email, name: user.name };
      },
    }),
  ],
  session: { strategy: "jwt" },
  callbacks: {
    jwt: async ({ token, user }) => {
      if (user) {
        token.id = user.id;
        token.email = user.email;
      }
      return token;
    },
    session: async ({ session, token }) => {
      if (token) {
        session.user.id = token.id as string;
        session.user.email = token.email;
      }
      return session;
    },
  },
  pages: {
    signIn: "/auth/signin",
  },
});

export { handler as GET, handler as POST };

Displaying User Tasks (RSC)

Our main dashboard will display tasks specific to the logged-in user. We'll use a React Server Component (RSC) to fetch this data directly from the database, reducing client-side JavaScript.

This page.tsx fetches tasks and passes them to a Client Component for rendering:

// app/dashboard/page.tsx
import { getServerSession } from "next-auth";
import { authOptions } from "@/app/api/auth/[...nextauth]/route"; // Adjust path
import { PrismaClient } from "@prisma/client";
import TaskList from "./TaskList"; // This will be a Client Component

const prisma = new PrismaClient();

export default async function DashboardPage() {
  const session = await getServerSession(authOptions);

  if (!session || !session.user?.id) {
    return <p>Please sign in to view your tasks.</p>;
  }

  const tasks = await prisma.task.findMany({
    where: { userId: session.user.id },
    orderBy: { createdAt: "desc" },
  });

  return (
    <div>
      <h1>Welcome, {session.user.name || session.user.email}!</h1>
      <h2>Your Tasks</h2>
      <TaskList initialTasks={tasks} />
    </div>
  );
}

Adding Tasks with Server Actions

To add new tasks, we'll leverage Next.js Server Actions. This allows us to handle form submissions directly on the server, ensuring security and automatically revalidating data in our UI.

Here's a simplified Server Action to create a new task:

// app/actions/task-actions.ts
"use server";

import { revalidatePath } from "next/cache";
import { getServerSession } from "next-auth";
import { authOptions } from "@/app/api/auth/[...nextauth]/route";
import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

export async function addTask(formData: FormData) {
  const session = await getServerSession(authOptions);

  if (!session || !session.user?.id) {
    throw new Error("Unauthorized");
  }

  const title = formData.get("title") as string;
  if (!title || title.trim() === "") {
    throw new Error("Task title cannot be empty.");
  }

  await prisma.task.create({
    data: {
      title,
      userId: session.user.id,
    },
  });

  revalidatePath("/dashboard"); // Revalidate dashboard to show new task
}

Interactive Task Management (RCC)

While Server Components handle initial data, Client Components (RCC) are essential for interactivity. We can use them to update the UI instantly, like marking a task complete, often with optimistic updates.

This TaskList component uses client-side state and a Server Action:

// app/dashboard/TaskList.tsx (Client Component)
"use client";

import { useState } from "react";
// Assume toggleTaskCompletion is another server action
import { toggleTaskCompletion } from "@/app/actions/task-actions"; 

export default function TaskList({ initialTasks }) {
  const [tasks, setTasks] = useState(initialTasks);

  const handleToggle = async (taskId: string, currentStatus: boolean) => {
    // Optimistic UI update
    setTasks(tasks.map(task =>
      task.id === taskId ? { ...task, completed: !currentStatus } : task
    ));
    try {
      await toggleTaskCompletion(taskId, !currentStatus);
    } catch (error) {
      console.error("Failed to toggle task:", error);
      // Revert if error
      setTasks(initialTasks); // Revert to initial state on error
    }
  };

  return (
    <ul>
      {tasks.map(task => (
        <li key={task.id}>
          <input
            type="checkbox"
            checked={task.completed}
            onChange={() => handleToggle(task.id, task.completed)}
          />
          <span style={{ textDecoration: task.completed ? "line-through" : "none" }}>
            {task.title}
          </span>
        </li>
      ))}
    </ul>
  );
}

Protecting Routes with Middleware

To secure our application, we must protect routes that require authentication. Next.js Middleware is ideal for this, allowing us to redirect unauthenticated users before they even reach a page.

Our middleware.ts can enforce authentication for specific paths:

// middleware.ts
import { withAuth } from "next-auth/middleware";
import { NextResponse } from "next/server";

export default withAuth(
  // `withAuth` augments the Next.js request with the user's token.
  function middleware(req) {
    const { pathname } = req.nextUrl;
    // Example: Only allow authenticated users to access /dashboard
    if (pathname.startsWith("/dashboard") && !req.nextauth.token) {
      return NextResponse.redirect(new URL("/auth/signin", req.url));
    }
    // Allow access to public pages or authenticated pages
    return NextResponse.next();
  },
  {
    callbacks: {
      authorized: ({ token }) => !!token, // Only allow if token exists
    },
    pages: {
      signIn: "/auth/signin",
    },
  }
);

export const config = {
  matcher: ["/dashboard/:path*", "/api/protected/:path*"], // Apply middleware to these paths
};

Loading & Error States

A robust fullstack app provides clear feedback. Next.js App Router helps with dedicated files for loading and error states:

  • loading.js: Automatically displays a loading UI for a route segment while its content is being fetched on the server.
  • error.js: Creates UI boundaries to catch and display errors gracefully for a route segment, preventing the whole app from crashing.

For Server Actions, you can also use client-side states like isPending from useFormStatus to show loading indicators.

The Fullstack Integration Flow

Let's visualize how these components work together in our task manager:

  • A user tries to access /dashboard.
  • middleware.ts checks their authentication status via NextAuth.js.
  • If authenticated, dashboard/page.tsx (an RSC) fetches user-specific tasks from Prisma.
  • TaskList.tsx (an RCC) renders these tasks and allows interactive toggling of completion status via a Server Action.
  • A separate form (RCC) uses another Server Action to add new tasks, which then revalidates the /dashboard path.

This seamless interaction creates a powerful and efficient user experience.

Fullstack Integration Check

Consider a Next.js fullstack application. Which of the following statements correctly describe the typical use of Server Components, Client Components, Server Actions, or Middleware?

Fullstack App Recap

You've just walked through the process of building a comprehensive fullstack Next.js application! We integrated several key features:

  • Prisma ORM for database interaction.
  • NextAuth.js for secure authentication.
  • React Server Components for efficient server-side data fetching.
  • Next.js Server Actions for secure data mutations and revalidation.
  • React Client Components for adding rich client-side interactivity.
  • Next.js Middleware for protecting routes.

This lesson ties together many foundational concepts, providing you with a robust understanding of how to build complex, production-ready Next.js applications.

常见问题解答

「构建全栈应用」课时是免费的吗?

是的 — 「构建全栈应用」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Next.js 15 Fullstack (App Router + Server Actions) 课程的其余内容,请升级到 CoddyKit PRO。 Next.js 15 Fullstack (App Router + Server Actions) 课程共包含 4 节课。

「构建全栈应用」这节课中我会学到什么?

完成一个综合项目,将所学概念整合到一个完整的全栈 Next.js 应用中。 你通过在浏览器中直接运行的动手代码来练习 Next.js 15 Fullstack (App Router + Server Actions),全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Next.js 15 Fullstack (App Router + Server Actions) 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Next.js 15 Fullstack (App Router + Server Actions) 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。

「构建全栈应用」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Next.js 15 Fullstack (App Router + Server Actions) 课中编写并运行代码吗?

能。每节 Next.js 15 Fullstack (App Router + Server Actions) 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 部署到 Vercel
  2. 构建全栈应用
  3. 项目复盘与最佳实践
  4. 生产环境监控与错误追踪
← 返回 Next.js 15 Fullstack (App Router + Server Actions)