知识库首页 模版 app-actions.ts.md

app actions.ts

本地来源:模版/template/模版文档对比说明/02-应用核心文件/app-actions.ts.md

app/actions.ts 详细分析

文件作用

app/actions.ts 是 Next.js App Router 的服务器动作文件,包含了用户认证、支付等服务器端操作函数。服务器动作是 Next.js 13+ 的新特性,允许在客户端直接调用服务器端函数。

逐行代码分析

"use server";

第1行: 服务器动作指令 - "use server" 是 Next.js 的指令,标记整个文件为服务器动作 - 这个文件中的所有函数都会在服务器端执行 - 可以直接在客户端组件中调用这些函数


第2行: 空行

import { encodedRedirect } from "@/utils/utils";

第3行: 导入重定向工具函数 - encodedRedirect 是自定义的重定向函数 - 可以在重定向时携带错误或成功消息 - 使用 URL 编码确保消息的安全传输

import { createClient } from "@/utils/supabase/server";

第4行: 导入 Supabase 服务器客户端 - createClient 创建服务器端的 Supabase 客户端 - 可以安全地访问服务器端 API 和数据库 - 在服务器动作中执行认证和数据操作

import { headers } from "next/headers";

第5行: 导入 Next.js 头部信息 - headers 函数获取当前请求的头部信息 - 在服务器端可以访问 origin、user-agent 等信息 - 用于生成回调 URL 等

import { redirect } from "next/navigation";

第6行: 导入 Next.js 重定向函数 - redirect 是 Next.js 的重定向函数 - 在服务器动作中用于导航到其他页面 - 支持绝对和相对 URL


第7行: 空行

export const signUpAction = async (formData: FormData) => {

第8行: 导出注册动作函数 - export const 导出常量函数 - signUpAction 处理用户注册 - async 异步函数,可以等待异步操作 - formData: FormData 参数类型,来自表单提交

  const email = formData.get("email")?.toString();

第9行: 获取邮箱地址 - formData.get("email") 从表单数据中获取邮箱字段 - ?.toString() 可选链调用,安全地转换为字符串 - 如果字段不存在,返回 undefined

  const password = formData.get("password")?.toString();

第10行: 获取密码 - 类似地从表单数据中获取密码字段 - 使用可选链确保安全的类型转换

  const supabase = await createClient();

第11行: 创建 Supabase 客户端 - await createClient() 异步创建客户端实例 - 在服务器端安全地访问 Supabase 服务

  const origin = (await headers()).get("origin");

第12行: 获取请求来源 - await headers() 获取请求头信息 - .get("origin") 获取请求的来源域名 - 用于构建回调 URL


第13行: 空行

  if (!email || !password) {

第14行: 验证输入数据 - 检查邮箱和密码是否都存在 - !email || !password 逻辑或运算,任一为空则为真

    return encodedRedirect(

第15行: 返回错误重定向 - 调用 encodedRedirect 函数进行重定向 - 这是一个错误情况的处理

      "error",

第16行: 错误类型 - "error" 表示这是一个错误消息 - 会在目标页面显示为错误提示

      "/sign-up",

第17行: 重定向目标 - "/sign-up" 重定向回注册页面 - 让用户可以重新填写表单

      "Email and password are required"

第18行: 错误消息 - 提示用户邮箱和密码都是必需的 - 这个消息会显示在注册页面

    );

第19行: 函数调用结束

  }

第20行: 验证条件结束


第21行: 空行

  const { error } = await supabase.auth.signUp({

第22行: 调用 Supabase 注册API - await supabase.auth.signUp() 异步调用注册功能 - 解构赋值只获取 error 字段 - 如果注册成功,error 为 null

    email,

第23行: 传递邮箱参数 - 使用 ES6 简写语法,等同于 email: email

    password,

第24行: 传递密码参数 - 同样使用简写语法

    options: {

第25行: 注册选项对象 - options 包含额外的注册配置

      emailRedirectTo: `${origin}/auth/callback`,

第26行: 邮件重定向URL - emailRedirectTo 设置邮件确认后的重定向地址 - ${origin}/auth/callback 使用模板字符串构建完整URL - 用户点击邮件中的链接后会跳转到这个地址

    },

第27行: 选项对象结束

  });

第28行: 注册API调用结束


第29行: 空行

  if (error) {

第30行: 检查是否有错误 - 如果注册过程中出现错误

    console.error(error.code + " " + error.message);

第31行: 记录错误信息 - console.error 在服务器控制台记录错误 - 拼接错误代码和错误消息

    return encodedRedirect("error", "/sign-up", error.message);

第32行: 返回错误重定向 - 重定向到注册页面并显示具体的错误消息 - error.message 是 Supabase 返回的错误信息

  } else {

第33行: 注册成功的情况

    return encodedRedirect("success", "/dashboard", "Thanks for signing up!");

第34行: 返回成功重定向 - "success" 表示成功消息 - 重定向到 /dashboard 仪表板页面 - 显示感谢注册的消息

  }

第35行: 条件语句结束

};

第36行: 注册函数结束


第37行: 空行

export const signInAction = async (formData: FormData) => {

第38行: 导出登录动作函数 - 处理用户登录的服务器动作

  const email = formData.get("email") as string;

第39行: 获取邮箱(类型断言) - 使用 as string 进行类型断言 - 确信这里的值一定是字符串类型

  const password = formData.get("password") as string;

第40行: 获取密码(类型断言)

  const supabase = await createClient();

第41行: 创建 Supabase 客户端


第42行: 空行

  const { error } = await supabase.auth.signInWithPassword({

第43行: 调用密码登录API - signInWithPassword 使用邮箱密码登录

    email,

第44行: 传递邮箱参数

    password,

第45行: 传递密码参数

  });

第46行: 登录API调用结束


第47行: 空行

  if (error) {

第48行: 检查登录错误

    return encodedRedirect("error", "/sign-in", error.message);

第49行: 返回登录错误 - 重定向回登录页面并显示错误消息

  }

第50行: 错误处理结束


第51行: 空行

  return redirect("/dashboard");

第52行: 登录成功重定向 - 直接重定向到仪表板,不需要显示消息

};

第53行: 登录函数结束


第54行: 空行

export const forgotPasswordAction = async (formData: FormData) => {

第55行: 导出忘记密码动作函数

  const email = formData.get("email")?.toString();

第56行: 获取邮箱地址

  const supabase = await createClient();

第57行: 创建客户端

  const origin = (await headers()).get("origin");

第58行: 获取请求来源

  const callbackUrl = formData.get("callbackUrl")?.toString();

第59行: 获取回调URL - 可选的回调地址,用于自定义重定向


第60行: 空行

  if (!email) {

第61行: 验证邮箱是否存在

    return encodedRedirect("error", "/forgot-password", "Email is required");

第62行: 邮箱为空的错误处理

  }

第63行: 验证结束


第64行: 空行

  const { error } = await supabase.auth.resetPasswordForEmail(email, {

第65行: 调用重置密码API - resetPasswordForEmail 发送重置密码邮件

    redirectTo: `${origin}/auth/callback?redirect_to=/dashboard/reset-password`,

第66行: 设置邮件重定向 - 用户点击邮件链接后的跳转地址 - 包含一个查询参数指定最终目标页面

  });

第67行: API调用结束


第68行: 空行

  if (error) {

第69行: 检查重置错误

    console.error(error.message);

第70行: 记录错误

    return encodedRedirect(

第71行: 返回错误重定向

      "error",

第72行: 错误类型

      "/forgot-password",

第73行: 重定向目标

      "Could not reset password"

第74行: 错误消息

    );

第75行: 重定向调用结束

  }

第76行: 错误处理结束


第77行: 空行

  if (callbackUrl) {

第78行: 检查是否有自定义回调URL

    return redirect(callbackUrl);

第79行: 重定向到自定义URL

  }

第80行: 条件结束


第81行: 空行

  return encodedRedirect(

第82行: 返回成功重定向

    "success",

第83行: 成功类型

    "/forgot-password",

第84行: 重定向目标

    "Check your email for a link to reset your password."

第85行: 成功消息 - 提示用户检查邮件

  );

第86行: 重定向调用结束

};

第87行: 忘记密码函数结束


第88行: 空行

export const resetPasswordAction = async (formData: FormData) => {

第89行: 导出重置密码动作函数

  const supabase = await createClient();

第90行: 创建客户端


第91行: 空行

  const password = formData.get("password") as string;

第92行: 获取新密码

  const confirmPassword = formData.get("confirmPassword") as string;

第93行: 获取确认密码


第94行: 空行

  if (!password || !confirmPassword) {

第95行: 验证密码字段 - 检查两个密码字段都存在

    encodedRedirect(

第96行: 密码为空的错误处理 - 注意:这里缺少 return,可能是代码错误

      "error",

第97行: 错误类型

      "/dashboard/reset-password",

第98行: 重定向目标

      "Password and confirm password are required"

第99行: 错误消息

    );

第100行: 重定向调用结束

  }

第101行: 验证结束


第102行: 空行

  if (password !== confirmPassword) {

第103行: 验证密码一致性

    encodedRedirect(

第104行: 密码不匹配的错误处理 - 同样缺少 return

      "error",

第105行: 错误类型

      "/dashboard/reset-password",

第106行: 重定向目标

      "Passwords do not match"

第107行: 错误消息

    );

第108行: 重定向调用结束

  }

第109行: 验证结束


第110行: 空行

  const { error } = await supabase.auth.updateUser({

第111行: 调用更新用户API - updateUser 更新用户信息,包括密码

    password: password,

第112行: 传递新密码

  });

第113行: API调用结束


第114行: 空行

  if (error) {

第115行: 检查更新错误

    encodedRedirect(

第116行: 更新失败的错误处理 - 同样缺少 return

      "error",

第117行: 错误类型

      "/dashboard/reset-password",

第118行: 重定向目标

      "Password update failed"

第119行: 错误消息

    );

第120行: 重定向调用结束

  }

第121行: 错误处理结束


第122行: 空行

  encodedRedirect("success", "/dashboard/reset-password", "Password updated");

第123行: 成功重定向 - 密码更新成功的处理 - 同样缺少 return

};

第124行: 重置密码函数结束


第125行: 空行

export const signOutAction = async () => {

第126行: 导出登出动作函数 - 不需要参数的简单登出函数

  const supabase = await createClient();

第127行: 创建客户端

  await supabase.auth.signOut();

第128行: 调用登出API - 清除用户会话

  return redirect("/sign-in");

第129行: 重定向到登录页 - 登出后回到登录页面

};

第130行: 登出函数结束


第131行: 空行

export async function createCheckoutSession(

第132行: 导出创建支付会话函数 - 使用 function 关键字而不是箭头函数

  productId: string,

第133行: 产品ID参数

  email: string,

第134行: 用户邮箱参数

  userId: string,

第135行: 用户ID参数

  productType: "subscription" | "credits",

第136行: 产品类型参数 - 联合类型,只能是订阅或积分

  credits_amount?: number,

第137行: 积分数量(可选) - 当产品类型是积分时使用

  discountCode?: string

第138行: 折扣码(可选)

) {

第139行: 函数参数结束

  try {

第140行: 异常处理开始

    const requestBody: any = {

第141行: 构建请求体 - 使用 any 类型(不是最佳实践)

      product_id: productId,

第142行: 设置产品ID

      // request_id: `${userId}-${Date.now()}`, // use Unique request ID if you need

第143行: 注释的请求ID - 可选的唯一请求标识符

      customer: {

第144行: 客户信息对象

        email: email,

第145行: 客户邮箱

      },

第146行: 客户信息结束

      metadata: {

第147行: 元数据对象 - 存储额外的信息

        user_id: userId,

第148行: 用户ID元数据

        product_type: productType,

第149行: 产品类型元数据

        credits: credits_amount || 0,

第150行: 积分数量元数据 - 如果没有提供则默认为0

      },

第151行: 元数据结束

    };

第152行: 请求体构建结束


第153行: 空行

    // 如果配置了成功重定向 URL,则添加到请求中

第154行: 中文注释说明

    if (process.env.CREEM_SUCCESS_URL) {

第155行: 检查环境变量 - 如果配置了成功URL

      requestBody.success_url = process.env.CREEM_SUCCESS_URL;

第156行: 添加成功URL到请求体

    }

第157行: 条件结束


第158行: 空行

    // 添加折扣码(如果有)

第159行: 中文注释

    if (discountCode) {

第160行: 检查折扣码

      requestBody.discount_code = discountCode;

第161行: 添加折扣码到请求体

    }

第162行: 条件结束


第163行: 空行

    const response = await fetch(process.env.CREEM_API_URL + "/checkouts", {

第164行: 发送HTTP请求 - 调用 Creem API 创建支付会话

      method: "POST",

第165行: 设置请求方法

      headers: {

第166行: 设置请求头

        "x-api-key": process.env.CREEM_API_KEY!,

第167行: API密钥头 - 使用非空断言操作符 !

        "Content-Type": "application/json",

第168行: 内容类型头

      },

第169行: 请求头结束

      body: JSON.stringify(requestBody),

第170行: 请求体 - 将对象序列化为JSON字符串

    });

第171行: 请求配置结束


第172行: 空行

    if (!response.ok) {

第173行: 检查响应状态

      throw new Error("Failed to create checkout session");

第174行: 抛出错误 - 如果响应不成功

    }

第175行: 状态检查结束


第176行: 空行

    const data = await response.json();

第177行: 解析响应JSON

    return data.checkout_url;

第178行: 返回支付URL - 返回 Creem 提供的支付页面URL

  } catch (error) {

第179行: 异常捕获

    console.error("Error creating checkout session:", error);

第180行: 记录错误

    throw error;

第181行: 重新抛出错误 - 让调用者处理错误

  }

第182行: 异常处理结束

}

第183行: 函数结束

重要概念解释

1. 服务器动作 (Server Actions)

  • Next.js 13+ 的新特性
  • 在服务器端执行,客户端直接调用
  • 自动处理序列化和网络传输
  • 比传统 API 路由更简洁

2. FormData 处理

  • 接收来自 HTML 表单的数据
  • 可以包含文件上传
  • 需要手动提取和验证字段

3. 错误处理模式

  • 使用 encodedRedirect 在URL中传递消息
  • 区分错误和成功消息
  • 提供用户友好的反馈

4. Supabase 认证流程

  • 注册需要邮件验证
  • 支持密码重置
  • 自动处理会话管理

代码质量问题

1. 缺少 return 语句

resetPasswordAction 函数中,多处 encodedRedirect 调用缺少 return,这可能导致函数继续执行。

2. 类型安全

  • 使用了 any 类型
  • 可以使用更具体的接口定义

3. 改进建议

// 更好的类型定义
interface CheckoutRequest {
  product_id: string;
  customer: {
    email: string;
  };
  metadata: {
    user_id: string;
    product_type: "subscription" | "credits";
    credits: number;
  };
  success_url?: string;
  discount_code?: string;
}

总结

这个文件展示了 Next.js 服务器动作的强大功能:

  1. 统一的认证处理 - 注册、登录、密码重置
  2. 支付集成 - 与 Creem API 的完整集成
  3. 错误处理 - 统一的错误消息和重定向
  4. 类型安全 - 使用 TypeScript 类型检查
  5. 服务器端安全 - 所有敏感操作在服务器端执行

这是现代全栈 React 应用的典型实现,将前端和后端逻辑无缝结合。

本文档为站内渲染。原始文件本地路径:saas/source/templates/模版-template-模版文档对比说明-02-应用核心文件-app-actions-ts-13cf0e.md(仅本地保留,不入库不部署)