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

在Next.js站点实现Load More分页按钮,解决大量文章加载崩溃问题

解决方案:Next.js + WordPress 博客实现「加载更多」分页

问题背景

基于Next.js搭建的博客采用WordPress作为API数据源,当文章数量超过500篇后,因一次性拉取1000篇文章的全量数据,导致博客频繁崩溃。需要实现「Load More」分页按钮,点击时仅获取指定数量的文章数据。

当前代码分析

1. 博客首页文件(index.tsx)

//index.tsx
/**
 * Caution: Consider this file when using NextJS or GatsbyJS
 *
 * You may delete this file and its occurrences from the project filesystem if you are using react-scripts
 */
import React from 'react';
import BlogSearch from 'views/BlogSearch';
import Main from 'layouts/Main';
import WithLayout from 'WithLayout';
import Head from 'next/head';
import { getBlogPosts } from '../../lib/api';

const BlogPage = ({ posts }): JSX.Element => {
  return (
    <>
      {' '}
      <Head>
        <title>Copy and Post - Blog</title>
        <meta
          property="og:title"
          content="Copy and Post - Blog"
          key="Copy and Post"
        />
      </Head>
      <WithLayout
        component={() => <BlogSearch posts={posts} />}
        layout={Main}
      />
    </>
  );
};

export default BlogPage;

export async function getStaticProps() {
  
  const posts = await getBlogPosts();

  return {
    props: {
      ...posts,
   

    },
  };
}

2. API功能文件(api.ts)

//api.ts
import { homepageQuery, blogPosts, blogPostBySlug } from './queries';

const API_URL = process.env.WP_API_URL;

async function fetchAPI(query, { variables = {} } = {}) {
  // Set up some headers to tell the fetch call
  // that this is an application/json type
  const headers = { 'Content-Type': 'application/json' };

  // build out the fetch() call using the API_URL
  // environment variable pulled in at the start
  // Note the merging of the query and variables
  const res = await fetch(API_URL, {
    method: 'POST',
    headers,
    body: JSON.stringify({ query, variables }),
  });

  // error handling work
  const json = await res.json();
  if (json.errors) {
    console.log(json.errors);
    console.log('error details', query, variables);
    throw new Error('Failed to fetch API');
  }
  return json.data;
}

export async function getHomepageSections() {
  const data = await fetchAPI(homepageQuery);
  return data;
}

export async function getBlogPosts() {
  const data = await fetchAPI(blogPosts);
  return data;
}

export async function getBlogPostBySlug(slug) {
  const data = await fetchAPI(blogPostBySlug, { variables: { id: slug } });
  return data;
}

3. 查询函数文件(query.ts)

//query.ts
export const homepageQuery = `
query HomepageQuery {
    homepageSections {
      edges {
        node {
          homepage {
            hero {
              animatedwords
              heading
              subtitle
            }
            callouts {
              title
              subtitle
              calloutone {
                title
                subtext
                image {
                  mediaItemUrl
                }
              }
              calloutthree {
                title
                subtext
                image {
                  mediaItemUrl
                }
              }
              callouttwo {
                title
                subtext
                image {
                  mediaItemUrl
                }
              }
            }
            icongrid {
              iconone {
                description
                icon
                title
              }
              iconfive {
                description
                icon
                title
              }
              iconfour {
                description
                icon
                title
              }
              iconsix {
                description
                icon
                title
              }
              iconthree {
                description
                icon
                title
              }
              icontwo {
                description
                icon
                title
              }
            }
          }
        }
      }
    }
  } 
`;

export const blogPosts = `
  query BlogPosts {
    posts(first: 1000) {
      edges {
        node {
          author {
            node {
              nickname
            }
          }
          date
          slug
          featuredImage {
            node {
              mediaItemUrl
            }
          }
          title
        }
      }
    }
  }
`;

export const blogPostBySlug = `
  query PostBySlug($id: ID!) {
    post(id: $id, idType: SLUG) {
      id
      content
      title
      author {
        node {
          nickname
          avatar {
            url
          }
        }
      }
      date
      featuredImage {
        node {
          mediaItemUrl
        }
      }
    }
  }
`;

4. 控制台报错截图

控制台截图

分步实现「加载更多」功能

步骤1:修改GraphQL查询,支持分页参数

更新query.ts中的blogPosts查询,添加游标分页参数和分页信息字段:

export const blogPosts = `
  query BlogPosts($first: Int!, $after: String) {
    posts(first: $first, after: $after) {
      edges {
        node {
          author {
            node {
              nickname
            }
          }
          date
          slug
          featuredImage {
            node {
              mediaItemUrl
            }
          }
          title
        }
      }
      pageInfo {
        hasNextPage
        endCursor
      }
    }
  }
`;

新增的pageInfo用于判断是否还有下一页,以及获取当前页的结束游标,作为下一页查询的起始标记。

步骤2:更新API函数,支持传入分页参数

修改api.ts中的getBlogPosts函数,接收分页参数:

export async function getBlogPosts(first = 10, after = null) {
  const data = await fetchAPI(blogPosts, { 
    variables: { 
      first, 
      after 
    } 
  });
  return data;
}

默认每次加载10篇文章,可根据需求调整数量。

步骤3:改造博客首页组件,实现客户端分页逻辑

更新index.tsx,添加状态管理和加载更多逻辑:

//index.tsx
import React, { useState } from 'react';
import BlogSearch from 'views/BlogSearch';
import Main from 'layouts/Main';
import WithLayout from 'WithLayout';
import Head from 'next/head';
import { getBlogPosts } from '../../lib/api';

const BlogPage = ({ initialPosts, initialPageInfo }): JSX.Element => {
  const [posts, setPosts] = useState(initialPosts.edges);
  const [pageInfo, setPageInfo] = useState(initialPageInfo);
  const [isLoading, setIsLoading] = useState(false);

  const loadMorePosts = async () => {
    if (!pageInfo.hasNextPage || isLoading) return;
    
    setIsLoading(true);
    try {
      const newData = await getBlogPosts(10, pageInfo.endCursor);
      setPosts(prev => [...prev, ...newData.posts.edges]);
      setPageInfo(newData.posts.pageInfo);
    } catch (error) {
      console.error('加载更多失败:', error);
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <>
      <Head>
        <title>Copy and Post - Blog</title>
        <meta
          property="og:title"
          content="Copy and Post - Blog"
          key="Copy and Post"
        />
      </Head>
      <WithLayout
        component={() => (
          <>
            <BlogSearch posts={{ edges: posts }} />
            {pageInfo.hasNextPage && (
              <button 
                onClick={loadMorePosts} 
                disabled={isLoading}
                style={{ padding: '10px 20px', marginTop: '20px', cursor: 'pointer' }}
              >
                {isLoading ? '加载中...' : '加载更多'}
              </button>
            )}
          </>
        )}
        layout={Main}
      />
    </>
  );
};

export default BlogPage;

export async function getStaticProps() {
  const data = await getBlogPosts(10);
  return {
    props: {
      initialPosts: data.posts,
      initialPageInfo: data.posts.pageInfo
    },
  };
}

核心修改点:

  • 用useState管理已加载文章、分页信息和加载状态
  • 实现loadMorePosts函数,点击时请求下一页数据并合并到现有列表
  • 初始仅加载10篇文章,避免一次性拉取大量数据
  • 根据分页信息动态显示/隐藏加载按钮,加载中时禁用按钮

步骤4:兼容BlogSearch组件

确保BlogSearch组件能正确遍历posts.edges渲染文章,现有逻辑无需额外修改即可兼容。

效果验证

部署后首页仅加载10篇文章,点击「加载更多」才会请求下一批数据,彻底解决全量加载导致的崩溃问题。当没有更多文章时,按钮会自动隐藏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 00:50:56