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

如何在Next.js 13 App Router的Server Components中实现WordPress GraphQL分页?

问题:Next.js App Router Server Component中实现WordPress GraphQL游标分页的Load More功能

我正在为WordPress网站构建Next.js前端,使用App Router和GraphQL插件获取数据。首页是Server Component,用来展示最新文章。当前代码能加载第一页文章,但无法在Server Component中实现基于GraphQL游标的“Load More”功能——因为Server Component不能使用useState/useEffect钩子,且WordPress GraphQL仅支持游标分页,之前尝试的查询字符串分页方案行不通。


解决方案:结合Server Component与Client Component实现分页

Server Component无法处理客户端交互,所以我们需要把分页的状态管理和点击事件交给Client Component,同时用Server Component获取初始数据,后续分页请求通过API Route或Server Action处理。

1. 调整GraphQL查询,获取分页关键信息

首先修改查询,让它返回pageInfo(包含下一页游标endCursor和是否还有更多内容hasNextPage),这样才能知道后续分页的起点和是否需要显示Load More按钮。

更新类型定义和getPosts函数:

interface Post {
    title: string;
    date: string;
    excerpt: string;
    featuredImage: {
        node: {
          sourceUrl: string;
        };
    };
    categories: {
        edges: {
            node: {
                name: string;
            };
        }[];
    };
    slug: string;
    author: {
        node: {
            name: string;
        };
    };
}

interface PostsResponse {
  nodes: Post[];
  pageInfo: {
    endCursor: string;
    hasNextPage: boolean;
  };
}

async function getPosts(cursor: string): Promise<PostsResponse> {
    const endpoint = process.env.WORDPRESS_API_URL || '';

    const response = await fetch(endpoint, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({
            query: `
                query GetPosts($cursor: String!) {
                    posts(first: 10, after: $cursor) {
                        nodes {
                            title
                            date
                            excerpt
                            featuredImage {
                                node {
                                    sourceUrl
                                }
                            }
                            categories {
                                edges {
                                    node {
                                        name
                                    }
                                }
                            }
                            slug
                            author {
                                node {
                                    name
                                }
                            }
                        }
                        pageInfo {
                          endCursor
                          hasNextPage
                        }
                    }
                }
            `,
            variables: {
                cursor: cursor,
            },
        }),
    });

    const data = await response.json();
    return data.data.posts;
}

2. 编写Client Component处理分页交互

创建一个带'use client'指令的组件,负责管理分页状态、处理Load More点击事件,以及渲染文章列表。

'use client';

import { useState } from 'react';
import PostCard from './post';
import styles from './page.module.scss';

interface Post {
    title: string;
    date: string;
    excerpt: string;
    featuredImage: {
        node: {
          sourceUrl: string;
        };
    };
    categories: {
        edges: {
            node: {
                name: string;
            };
        }[];
    };
    slug: string;
    author: {
        node: {
            name: string;
        };
    };
}

interface PostsPaginationProps {
  initialPosts: Post[];
  initialEndCursor: string;
  hasNextPage: boolean;
}

export default function PostsPagination({ initialPosts, initialEndCursor, hasNextPage }: PostsPaginationProps) {
  const [posts, setPosts] = useState<Post[]>(initialPosts);
  const [currentCursor, setCurrentCursor] = useState(initialEndCursor);
  const [hasMore, setHasMore] = useState(hasNextPage);
  const [isLoading, setIsLoading] = useState(false);

  const loadMore = async () => {
    if (!hasMore || isLoading) return;
    setIsLoading(true);

    try {
      // 调用API Route获取下一页数据
      const response = await fetch('/api/load-posts', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ cursor: currentCursor }),
      });

      const data = await response.json();
      setPosts(prev => [...prev, ...data.nodes]);
      setCurrentCursor(data.pageInfo.endCursor);
      setHasMore(data.pageInfo.hasNextPage);
    } catch (error) {
      console.error('加载更多文章失败:', error);
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <div>
      <div className={styles.postsList}>
        {posts.map((post) => (
          <PostCard post={post} key={post.slug} />
        ))}
      </div>
      {hasMore && (
        <button onClick={loadMore} disabled={isLoading}>
          {isLoading ? '加载中...' : '加载更多'}
        </button>
      )}
    </div>
  );
}

3. 创建API Route处理分页请求

在app/api/load-posts/route.ts中新建API路由,用来接收Client Component的请求,调用getPosts获取下一页数据:

import { NextResponse } from 'next/server';
import { getPosts } from '@/app/utils/getPosts'; // 建议把getPosts抽离到utils目录

export async function POST(request: Request) {
  const { cursor } = await request.json();
  const postsData = await getPosts(cursor);
  return NextResponse.json(postsData);
}

4. 修改首页Server Component

将首页的Server Component改为渲染上面的Client Component,传递初始的文章、游标和分页状态:

import PostsPagination from './PostsPagination';
import { getPosts } from './utils/getPosts';

export default async function HomePage() {
    const postsData = await getPosts("");

    return (
        <PostsPagination 
          initialPosts={postsData.nodes} 
          initialEndCursor={postsData.pageInfo.endCursor} 
          hasNextPage={postsData.pageInfo.hasNextPage} 
        />
    );
}

替代方案:使用Server Actions简化流程

如果不想维护API Route,可以用Next.js的Server Actions直接在Client Component中调用Server端函数:

  1. 把getPosts移到utils/getPosts.ts,并添加'use server'指令:
'use server';

// 这里是之前的PostsResponse、Post接口和getPosts函数
  1. 修改Client Component的loadMore函数,直接调用Server Action:
'use client';

// ... 其他导入和接口定义不变
import { getPosts } from '@/utils/getPosts';

export default function PostsPagination({ initialPosts, initialEndCursor, hasNextPage }: PostsPaginationProps) {
  // ... 状态定义不变

  const loadMore = async () => {
    if (!hasMore || isLoading) return;
    setIsLoading(true);

    try {
      const postsData = await getPosts(currentCursor);
      setPosts(prev => [...prev, ...postsData.nodes]);
      setCurrentCursor(postsData.pageInfo.endCursor);
      setHasMore(postsData.pageInfo.hasNextPage);
    } catch (error) {
      console.error('加载更多文章失败:', error);
    } finally {
      setIsLoading(false);
    }
  };

  // ... 渲染部分不变
}

这样就省去了API Route的编写,代码更简洁。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 05:47:05