中文

一份关于 Next.js 14 服务器操作的全面指南,涵盖表单处理最佳实践、数据验证、安全考量以及构建现代 Web 应用的高级技术。

Next.js 14 服务器操作:掌握表单处理最佳实践

Next.js 14 引入了众多强大功能,用于构建高性能和用户友好的 Web 应用程序。其中,服务器操作(Server Actions)作为一种直接在服务器上处理表单提交和数据变更的革命性方式脱颖而出。本指南全面概述了 Next.js 14 中的服务器操作,重点关注表单处理、数据验证、安全性和高级技术的最佳实践。我们将通过实际示例,提供可行的见解,帮助您构建稳健且可扩展的 Web 应用程序。

什么是 Next.js 服务器操作?

服务器操作是在服务器上运行的异步函数,可以直接从 React 组件中调用。它们消除了传统上用于处理表单提交和数据变更的 API 路由,从而简化了代码、提高了安全性并增强了性能。服务器操作是 React 服务器组件(RSC),这意味着它们在服务器上执行,从而加快了初始页面加载速度并改善了 SEO。

服务器操作的主要优势:

设置您的 Next.js 14 项目

在深入研究服务器操作之前,请确保您已设置好一个 Next.js 14 项目。如果您是从零开始,请使用以下命令创建一个新项目:

npx create-next-app@latest my-next-app

请确保您的项目使用 app 目录结构,以充分利用服务器组件和操作的优势。

使用服务器操作进行基本表单处理

让我们从一个简单的例子开始:一个提交数据以在数据库中创建新项目的表单。我们将使用一个带输入字段和提交按钮的简单表单。

示例:创建一个新项目

首先,在您的 React 组件中定义一个服务器操作函数。此函数将在服务器上处理表单提交逻辑。

// app/components/CreateItemForm.tsx
'use client';

import { useState } from 'react';

async function createItem(formData: FormData) {
  'use server'

  const name = formData.get('name') as string;

  // 模拟数据库交互
  console.log('Creating item:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // 模拟延迟

  console.log('Item created successfully!');
}

export default function CreateItemForm() {
  const [isSubmitting, setIsSubmitting] = useState(false);
  
  async function handleSubmit(formData: FormData) {
    setIsSubmitting(true);
    await createItem(formData);
    setIsSubmitting(false);
  }

  return (
    
); }

代码解析:

数据验证

数据验证对于确保数据完整性和防止安全漏洞至关重要。服务器操作为执行服务器端验证提供了一个绝佳的机会。这种方法有助于降低仅依赖客户端验证所带来的风险。

示例:验证输入数据

修改 createItem 服务器操作以包含验证逻辑。

// app/components/CreateItemForm.tsx
'use client';

import { useState } from 'react';

async function createItem(formData: FormData) {
  'use server'

  const name = formData.get('name') as string;

  if (!name || name.length < 3) {
    throw new Error('Item name must be at least 3 characters long.');
  }

  // 模拟数据库交互
  console.log('Creating item:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // 模拟延迟

  console.log('Item created successfully!');
}

export default function CreateItemForm() {
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [errorMessage, setErrorMessage] = useState(null);
  
  async function handleSubmit(formData: FormData) {
    setIsSubmitting(true);
    setErrorMessage(null);
    try {
      await createItem(formData);
    } catch (error: any) {
      setErrorMessage(error.message || 'An error occurred.');
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    
{errorMessage &&

{errorMessage}

}
); }

代码解析:

使用验证库

对于更复杂的验证场景,可以考虑使用像下面这样的验证库:

以下是使用 Zod 的示例:

// app/utils/validation.ts
import { z } from 'zod';

export const CreateItemSchema = z.object({
  name: z.string().min(3, 'Item name must be at least 3 characters long.'),
});
// app/components/CreateItemForm.tsx
'use client';

import { useState } from 'react';
import { CreateItemSchema } from '../utils/validation';

async function createItem(formData: FormData) {
  'use server'

  const name = formData.get('name') as string;

  const validatedFields = CreateItemSchema.safeParse({ name });

  if (!validatedFields.success) {
    return { errors: validatedFields.error.flatten().fieldErrors };
  }

  // 模拟数据库交互
  console.log('Creating item:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // 模拟延迟

  console.log('Item created successfully!');
}

export default function CreateItemForm() {
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [errorMessage, setErrorMessage] = useState(null);
  
  async function handleSubmit(formData: FormData) {
    setIsSubmitting(true);
    setErrorMessage(null);
    try {
      await createItem(formData);
    } catch (error: any) {
      setErrorMessage(error.message || 'An error occurred.');
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    
{errorMessage &&

{errorMessage}

}
); }

代码解析:

安全注意事项

服务器操作通过在服务器上执行代码来增强安全性,但遵循安全最佳实践以保护您的应用程序免受常见威胁仍然至关重要。

防止跨站请求伪造 (CSRF)

CSRF 攻击利用了网站对用户浏览器的信任。为防止 CSRF 攻击,请实施 CSRF 保护机制。

在使用服务器操作时,Next.js 会自动处理 CSRF 保护。该框架会为每次表单提交生成并验证一个 CSRF 令牌,确保请求源自您的应用程序。

处理用户认证和授权

确保只有授权用户才能执行某些操作。实施认证和授权机制以保护敏感数据和功能。

以下是使用 NextAuth.js 保护服务器操作的示例:

// app/components/CreateItemForm.tsx
'use client';

import { useState } from 'react';
import { getServerSession } from 'next-auth';
import { authOptions } from '../../app/api/auth/[...nextauth]/route';

async function createItem(formData: FormData) {
  'use server'

  const session = await getServerSession(authOptions);

  if (!session) {
    throw new Error('Unauthorized');
  }

  const name = formData.get('name') as string;

  // 模拟数据库交互
  console.log('Creating item:', name, 'by user:', session.user?.email);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // 模拟延迟

  console.log('Item created successfully!');
}

export default function CreateItemForm() {
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [errorMessage, setErrorMessage] = useState(null);
  
  async function handleSubmit(formData: FormData) {
    setIsSubmitting(true);
    setErrorMessage(null);
    try {
      await createItem(formData);
    } catch (error: any) {
      setErrorMessage(error.message || 'An error occurred.');
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    
{errorMessage &&

{errorMessage}

}
); }

代码解析:

净化输入数据

净化输入数据以防止跨站脚本(XSS)攻击。当恶意代码被注入网站时,就会发生 XSS 攻击,这可能会危及用户数据或应用程序功能。

在服务器操作中处理用户提供的输入之前,请使用像 DOMPurifysanitize-html 这样的库来净化输入。

高级技术

既然我们已经涵盖了基础知识,让我们来探讨一些有效使用服务器操作的高级技术。

乐观更新

乐观更新通过立即更新 UI(就像操作一定会成功一样)来提供更好的用户体验,甚至在服务器确认之前就进行更新。如果操作在服务器上失败,UI 将恢复到其先前的状态。

// app/components/UpdateItemForm.tsx
'use client';

import { useState } from 'react';

async function updateItem(id: string, formData: FormData) {
  'use server'

  const name = formData.get('name') as string;

  // 模拟数据库交互
  console.log('Updating item:', id, 'with name:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // 模拟延迟

  // 模拟失败(用于演示)
  const shouldFail = Math.random() < 0.5;
  if (shouldFail) {
    throw new Error('Failed to update item.');
  }

  console.log('Item updated successfully!');
  return { name }; // 返回更新后的名称
}

export default function UpdateItemForm({ id, initialName }: { id: string; initialName: string }) {
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [errorMessage, setErrorMessage] = useState(null);
  const [itemName, setItemName] = useState(initialName);

  async function handleSubmit(formData: FormData) {
    setIsSubmitting(true);
    setErrorMessage(null);

    // 乐观地更新 UI
    const newName = formData.get('name') as string;
    setItemName(newName);

    try {
      const result = await updateItem(id, formData);
      // 如果成功,更新已通过 setItemName 反映在 UI 中

    } catch (error: any) {
      setErrorMessage(error.message || 'An error occurred.');
      // 发生错误时恢复 UI
      setItemName(initialName);
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    

Current Name: {itemName}

{errorMessage &&

{errorMessage}

}
); }

代码解析:

重新验证数据

在服务器操作修改数据后,您可能需要重新验证缓存的数据,以确保 UI 反映最新的更改。Next.js 提供了几种重新验证数据的方法:

以下是在创建新项目后重新验证路径的示例:

// app/components/CreateItemForm.tsx
'use client';

import { useState } from 'react';
import { revalidatePath } from 'next/cache';

async function createItem(formData: FormData) {
  'use server'

  const name = formData.get('name') as string;

  // 模拟数据库交互
  console.log('Creating item:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // 模拟延迟

  console.log('Item created successfully!');

  revalidatePath('/items'); // 重新验证 /items 路径
}

export default function CreateItemForm() {
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [errorMessage, setErrorMessage] = useState(null);
  
  async function handleSubmit(formData: FormData) {
    setIsSubmitting(true);
    setErrorMessage(null);
    try {
      await createItem(formData);
    } catch (error: any) {
      setErrorMessage(error.message || 'An error occurred.');
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    
{errorMessage &&

{errorMessage}

}
); }

代码解析:

服务器操作的最佳实践

为最大化服务器操作的优势,请考虑以下最佳实践:

常见陷阱及规避方法

虽然服务器操作提供了众多优势,但仍有一些常见的陷阱需要注意:

结论

Next.js 14 服务器操作提供了一种强大而高效的方式,可以直接在服务器上处理表单提交和数据变更。通过遵循本指南中概述的最佳实践,您可以构建稳健、安全且高性能的 Web 应用程序。拥抱服务器操作,以简化您的代码、增强安全性并改善整体用户体验。在整合这些原则时,请考虑您的开发选择对全球范围的影响。确保您的表单和数据处理流程对于不同国家和地区的用户而言是易于访问、安全且友好的。这种对包容性的承诺不仅会提高您应用程序的可用性,还将在全球范围内扩大其影响力和有效性。