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

如何配置Next.js 13 App目录中依赖useEffect的服务端组件并实现预渲染?

解决Next.js 13 App目录中服务端组件获取数据并确保预渲染的问题

你现在的问题核心是混淆了服务端组件(Server Component)和客户端组件(Client Component)的职责——服务端组件根本不需要用useEffect来获取数据,它可以直接在服务器上执行异步代码,完成数据获取和渲染后再把HTML发送给客户端。下面我给你两种解决方案,分别对应不同的数据量场景:

方案一:服务端获取全量数据,客户端处理分页交互

这种方案适合数据量不大的情况,一次性把所有数据拿到服务端,再交给客户端组件处理分页切换的交互:

1. 改造主组件为服务端组件

把原来的Newsletters改成服务端组件,直接在顶层用async/await获取数据,不需要useState和useEffect:

import Navbar from '@/components/navbar'
import Footer from '@/components/footer'
import NewsletterClient from './newsletter-client';
import axios from 'axios';

// 默认就是服务端组件,无需添加"use client"
const Newsletters = async () => {
    // 直接在服务端发起请求,数据获取完成后再渲染
    const res = await axios.get(`${process.env.NEXT_PUBLIC_BACKEND_API}/newsletters`);
    const allIssues = res.data;

    return (
        <>
            <Navbar />
            <div className="newsletter-container" id='newsletter-container'>
                <h1>Newsletters</h1>
                <hr></hr>
                {/* 将全量数据传给客户端组件处理交互 */}
                <NewsletterClient allIssues={allIssues} />
            </div>
            <Footer />
        </>
    );
}

export default Newsletters;

2. 创建客户端组件处理交互

新建newsletter-client.js,专门处理分页状态和点击事件,必须添加'use client'标识:

'use client'; // 标记为客户端组件,支持React状态和事件

import Pagination from './pagination';
import IssueCards from './issueCards';
import { useState } from 'react';

const NewsletterClient = ({ allIssues }) => {
    const [currentPage, setCurrentPage] = useState(1);
    const issuesPerPage = 5; // 可根据需求调整,也可以作为props传入

    // 切换页码的逻辑
    const paginate = (pageNumber) => setCurrentPage(pageNumber);

    // 计算当前页需要展示的数据
    const indexOfLastIssue = currentPage * issuesPerPage;
    const indexOfFirstIssue = indexOfLastIssue - issuesPerPage;
    const currentIssues = allIssues.slice(indexOfFirstIssue, indexOfLastIssue);

    return (
        <div className="newsletter-wrapper">
            <IssueCards issues={currentIssues} />
            <Pagination 
                issuesPerPage={issuesPerPage} 
                totalIssues={allIssues.length} 
                paginate={paginate} 
            />
        </div>
    );
};

export default NewsletterClient;

方案二:服务端分页(适合大数据量场景)

如果你的新闻通讯数据很多,一次性加载全量数据会影响性能,可以用URL搜索参数实现服务端分页,每次切换页码都从服务端获取对应页的数据:

1. 服务端组件根据搜索参数获取数据

import Navbar from '@/components/navbar'
import Footer from '@/components/footer'
import Pagination from './pagination';
import IssueCards from './issueCards';
import axios from 'axios';

// 接收searchParams参数,获取URL中的page参数
const Newsletters = async ({ searchParams }) => {
    const currentPage = parseInt(searchParams.page) || 1;
    const issuesPerPage = 5;

    // 向后端请求对应页的数据(假设你的API支持page和limit参数)
    const res = await axios.get(`${process.env.NEXT_PUBLIC_BACKEND_API}/newsletters`, {
        params: {
            page: currentPage,
            limit: issuesPerPage
        }
    });
    const { issues, totalIssues } = res.data; // 假设后端返回当前页数据和总条数

    return (
        <>
            <Navbar />
            <div className="newsletter-container" id='newsletter-container'>
                <h1>Newsletters</h1>
                <hr></hr>
                <div className="newsletter-wrapper">
                    <IssueCards issues={issues} />
                    {/* 把当前页、总条数等参数传给分页组件 */}
                    <Pagination 
                        currentPage={currentPage}
                        issuesPerPage={issuesPerPage} 
                        totalIssues={totalIssues} 
                    />
                </div>
            </div>
            <Footer />
        </>
    );
}

export default Newsletters;

2. 客户端分页组件处理URL跳转

修改Pagination组件为客户端组件,通过修改URL的搜索参数触发服务端重新渲染:

'use client';

import { useRouter, useSearchParams } from 'next/navigation';

const Pagination = ({ currentPage, issuesPerPage, totalIssues }) => {
    const router = useRouter();
    const searchParams = useSearchParams();

    const paginate = (pageNumber) => {
        const newSearchParams = new URLSearchParams(searchParams);
        newSearchParams.set('page', pageNumber);
        // 修改URL,触发服务端重新获取对应页的数据
        router.push(`/newsletters?${newSearchParams.toString()}`);
    };

    // 生成页码数组
    const pageNumbers = [];
    for (let i = 1; i <= Math.ceil(totalIssues / issuesPerPage); i++) {
        pageNumbers.push(i);
    }

    return (
        <nav>
            <ul className="pagination">
                {pageNumbers.map(number => (
                    <li key={number} className={currentPage === number ? 'active' : ''}>
                        <button onClick={() => paginate(number)}>
                            {number}
                        </button>
                    </li>
                ))}
            </ul>
        </nav>
    );
};

export default Pagination;

关键知识点总结

  • 服务端组件默认无需标识:Next.js 13 App目录下的组件默认都是服务端组件,不需要'use client',可以直接在顶层使用async/await获取数据,数据获取完成后才会渲染HTML发送给客户端,确保内容预渲染完成。
  • 拆分职责:服务端组件负责数据获取和静态内容渲染,客户端组件负责交互逻辑(状态管理、事件处理),这样既保证了SEO和首屏加载速度,又能实现交互功能。
  • 环境变量使用:NEXT_PUBLIC_前缀的环境变量在服务端和客户端都能访问,如果是服务端专用的变量(不带前缀),也可以在服务端组件中直接使用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 10:40:39