You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js 13服务器组件中从API路由获取数据的疑问

Next.js 13:服务器组件中通过API路由获取PocketBase数据的最佳实践

我正在学习Next.js 13,尝试从PocketBase数据库获取数据并展示在表格中。已经创建了连接数据库并获取数据的API路由,但在服务器组件中获取数据时存在困惑。

项目结构

app/
  api/
    ingresos/
      route.ts
  components/
    tablaIngresosCliente.tsx
  ingresos/
    page.tsx
  ...

API路由代码(app/api/ingresos/route.ts)

import { NextResponse } from 'next/server';
import PocketBase from 'pocketbase';

export async function GET(request: Request) {
  const pb = new PocketBase('http://127.0.0.1:8090');
  try {
    const records = await pb.collection('ingresos').getFullList({
      sort: '-created',
    });
    return NextResponse.json(records);
    
  } catch (error) {
    console.error(error)
    return new Response('Could not fetch', { status: 500 })
  }      
}

服务器组件代码(app/components/tablaIngresosCliente.tsx)

export default async function TablaIngresos() {
    const res = await fetch('/api/ingresos');
    const ingresos = await res.json();

    return (
        <div className="flex flex-col">
            <div className="overflow-x-auto sm:-mx-6 lg:-mx-8">
                <div className="inline-block min-w-full py-2 sm:px-6 lg:px-8">
                    <div className="overflow-hidden">
                        <table className="min-w-full text-center text-sm font-light">
                            <thead className="border-b bg-neutral-800 font-medium text-white dark:border-neutral-500 dark:bg-neutral-900">
                                <tr>
                                    <th scope="col" className=" px-6 py-4">#</th>
                                    <th scope="col" className=" px-6 py-4">Fuente</th>
                                    <th scope="col" className=" px-6 py-4">Cantidad</th>
                                    <th scope="col" className=" px-6 py-4">Fecha</th>
                                </tr>
                            </thead>
                            <tbody>
                                {ingresos.map((item, index) => (
                                    <tr key={index} className="border-b dark:border-neutral-500">
                                        <td className="whitespace-nowrap  px-6 py-4 font-medium">{index + 1}</td>
                                        <td className="whitespace-nowrap  px-6 py-4">{item.fuente}</td>
                                        <td className="whitespace-nowrap  px-6 py-4">{item.cantidad}</td>
                                        <td className="whitespace-nowrap  px-6 py-4">{item.fecha}</td>
                                    </tr>
                                ))}
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        </div>
    );
}

当前访问/api/ingresos路由能得到正确响应,但不确定在服务器组件中这样调用API路由是否合理。了解可以使用服务器组件Actions,但选择API路由是因为计划用客户端组件发送POST请求添加数据,认为API路由可同时处理GET和POST请求。想知道:

  1. 这是否是Next.js 13中获取数据的正确方式?
  2. 是否推荐在服务器组件中从API路由获取数据?
  3. 有没有更好的数据获取与展示方式?

回答

1. 当前方式是否正确?

你的代码本身可以正常运行:服务器组件中使用fetch调用内部API路由时,Next.js会在服务器端直接执行该请求(不会发起外部网络请求),最终能正确渲染数据表格。但这不是最优化的实现方式。

2. 是否推荐服务器组件调用内部API路由?

不推荐,核心原因如下:

  • 冗余层级:服务器组件本身运行在服务器端,直接调用PocketBase SDK获取数据,比通过API路由中转少一层处理,性能更优。
  • 重复逻辑:API路由和服务器组件都需要初始化PocketBase实例,导致代码重复,维护成本增加。
  • 调试复杂度:多一层API路由意味着排查问题时需要多检查一个环节,增加调试难度。

3. 更好的实现方案

结合你需要同时支持客户端POST请求的需求,推荐以下两种方案:

方案一:共享数据获取逻辑

将PocketBase的初始化和数据获取逻辑抽离为独立工具函数,在API路由和服务器组件中复用:

  1. 创建工具文件app/lib/pocketbase.ts:
import PocketBase from 'pocketbase';

export function getPocketBase() {
  return new PocketBase('http://127.0.0.1:8090');
}

export async function getIngresos() {
  const pb = getPocketBase();
  try {
    return await pb.collection('ingresos').getFullList({
      sort: '-created',
    });
  } catch (error) {
    console.error(error);
    throw new Error('Could not fetch ingresos');
  }
}
  1. 修改API路由app/api/ingresos/route.ts:
import { NextResponse } from 'next/server';
import { getIngresos, getPocketBase } from '@/app/lib/pocketbase';

export async function GET(request: Request) {
  try {
    const records = await getIngresos();
    return NextResponse.json(records);
  } catch (error) {
    return new Response('Could not fetch', { status: 500 });
  }
}

// 新增POST请求处理逻辑
export async function POST(request: Request) {
  const pb = getPocketBase();
  try {
    const data = await request.json();
    const record = await pb.collection('ingresos').create(data);
    return NextResponse.json(record, { status: 201 });
  } catch (error) {
    return new Response('Could not create record', { status: 500 });
  }
}
  1. 修改服务器组件app/components/tablaIngresosCliente.tsx:
import { getIngresos } from '@/app/lib/pocketbase';

export default async function TablaIngresos() {
    const ingresos = await getIngresos();

    return (
        <div className="flex flex-col">
            <div className="overflow-x-auto sm:-mx-6 lg:-mx-8">
                <div className="inline-block min-w-full py-2 sm:px-6 lg:px-8">
                    <div className="overflow-hidden">
                        <table className="min-w-full text-center text-sm font-light">
                            <thead className="border-b bg-neutral-800 font-medium text-white dark:border-neutral-500 dark:bg-neutral-900">
                                <tr>
                                    <th scope="col" className=" px-6 py-4">#</th>
                                    <th scope="col" className=" px-6 py-4">Fuente</th>
                                    <th scope="col" className=" px-6 py-4">Cantidad</th>
                                    <th scope="col" className=" px-6 py-4">Fecha</th>
                                </tr>
                            </thead>
                            <tbody>
                                {ingresos.map((item) => (
                                    <tr key={item.id} className="border-b dark:border-neutral-500">
                                        <td className="whitespace-nowrap px-6 py-4 font-medium">{item.id}</td>
                                        <td className="whitespace-nowrap px-6 py-4">{item.fuente}</td>
                                        <td className="whitespace-nowrap px-6 py-4">{item.cantidad}</td>
                                        <td className="whitespace-nowrap px-6 py-4">{new Date(item.fecha).toLocaleDateString()}</td>
                                    </tr>
                                ))}
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        </div>
    );
}

优化点说明:

  • 用item.id代替index作为React元素的key,符合React最佳实践,避免列表更新时出现异常。
  • 对日期字段做格式化处理,提升前端展示的可读性。
  • 共享逻辑避免代码重复,提升代码维护性。

方案二:使用Server Actions处理POST请求

如果不需要对外暴露API路由,可以直接使用Server Actions处理客户端的POST请求,同时在服务器组件中直接获取数据:

  1. 服务器组件代码同方案一的组件实现。
  2. 创建客户端组件处理表单提交:
'use client';

import { getPocketBase } from '@/app/lib/pocketbase';
import { useFormState } from 'react-dom';

async function addIngreso(formData: FormData) {
  'use server';
  const pb = getPocketBase();
  try {
    await pb.collection('ingresos').create({
      fuente: formData.get('fuente'),
      cantidad: formData.get('cantidad'),
      fecha: formData.get('fecha'),
    });
    return { success: true };
  } catch (error) {
    return { success: false, error: 'Failed to add ingreso' };
  }
}

export default function IngresoForm() {
  const [state, formAction] = useFormState(addIngreso, { success: false });

  return (
    <form action={formAction} className="space-y-4">
      <div>
        <label>Fuente:</label>
        <input type="text" name="fuente" required className="ml-2 p-1 border" />
      </div>
      <div>
        <label>Cantidad:</label>
        <input type="number" name="cantidad" required className="ml-2 p-1 border" />
      </div>
      <div>
        <label>Fecha:</label>
        <input type="date" name="fecha" required className="ml-2 p-1 border" />
      </div>
      <button type="submit" className="px-4 py-2 bg-blue-500 text-white rounded">Agregar Ingreso</button>
      {state.error && <p className="text-red-500 mt-2">{state.error}</p>}
      {state.success && <p className="text-green-500 mt-2">Ingreso agregado exitosamente!</p>}
    </form>
  );
}

优势:

  • 无需维护额外的API路由,代码结构更简洁。
  • Server Actions内置CSRF保护,安全性更高。
  • 表单状态处理更便捷,无需手动编写fetch请求逻辑。

总结

  • 你的当前实现可以运行,但存在冗余,不推荐服务器组件调用内部API路由。
  • 优先选择共享数据获取逻辑的方案,兼顾API路由的对外能力和服务器组件的性能。
  • 如果不需要对外暴露API,Server Actions是更轻量、安全的选择。

内容的提问来源于stack exchange,提问作者Vladimirzb

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 12:04:54