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

Next.js中共享持久化布局的页面如何仅单次服务端数据获取?

在Next.js 12.2.2中实现持久化布局的服务端数据复用

针对你遇到的持久化布局重复请求数据、SEO需求的问题,以下是几个可行的解决方案:

方案1:抽离共享数据逻辑并添加服务端缓存

这是最直接的解决方式,通过公共函数复用数据获取逻辑,结合缓存避免相同contractaddress的重复请求,既保证服务端渲染(SEO友好),又降低服务器压力。

步骤1:创建带缓存的公共数据获取函数

在项目中新建utils/fetchCollection.ts:

// 定义集合数据类型,可根据实际结构调整
type Collection = {
  contractAddress: string;
  name: string;
  description: string;
  image: string;
};

// 内存缓存,若部署多实例可替换为Redis等分布式缓存
const collectionCache = new Map<string, { data: Collection; expiresAt: number }>();
const CACHE_TTL = 5 * 60 * 1000; // 缓存5分钟,可按需调整

export async function fetchCollection(contractAddress: string): Promise<Collection> {
  // 检查缓存是否有效
  const cached = collectionCache.get(contractAddress);
  if (cached && Date.now() < cached.expiresAt) {
    return cached.data;
  }

  // 缓存失效时重新请求
  const res = await fetch(`http://localhost:3001/${contractAddress}`);
  if (!res.ok) {
    throw new Error('获取集合数据失败');
  }
  const data = await res.json();

  // 更新缓存
  collectionCache.set(contractAddress, {
    data,
    expiresAt: Date.now() + CACHE_TTL,
  });

  return data;
}

步骤2:在页面中复用该函数

修改dashboard.tsx、items.tsx、analytics.tsx的getServerSideProps:

export const getServerSideProps: GetServerSideProps = async (context) => {
  const contractAddress = context.params?.contractaddress as string;
  const collection = await fetchCollection(contractAddress);
  
  return { props: { collection } };
};

后续同一contractaddress的请求会直接复用缓存数据,无需重复调用后端API。

方案2:嵌套路由+父页面统一获取数据

将[contractaddress]设计为父页面,内部嵌套所有子视图,让父页面一次性获取数据并传递给布局和子视图,实现单请求复用,同时保持布局状态持久化。

步骤1:重构页面结构

调整文件夹结构:

├── /collection
│   ├──[contractaddress]
│   │  ├── index.tsx       // 父页面,负责数据获取和布局渲染
│   │  ├── views
│   │  │  ├── dashboard.tsx
│   │  │  ├── items.tsx
│   │  │  ├── analytics.tsx

步骤2:实现父页面index.tsx

import { GetServerSideProps } from "next";
import { useRouter } from "next/router";
import type { ReactElement } from "react";
import { CollectionLayout } from "../../../../layouts/CollectionLayout";
import Dashboard from "./views/dashboard";
import Items from "./views/items";
import Analytics from "./views/analytics";

type Props = {
  collection: {
    contractAddress: string;
    name: string;
    description: string;
    image: string;
  };
};

const CollectionView: React.FC<Props> = ({ collection }) => {
  const router = useRouter();
  const { view = "dashboard" } = router.query;

  // 根据路由参数渲染对应子视图
  const renderView = () => {
    switch (view) {
      case "items":
        return <Items />;
      case "analytics":
        return <Analytics />;
      default:
        return <Dashboard />;
    }
  };

  return (
    <CollectionLayout collection={collection}>
      {renderView()}
    </CollectionLayout>
  );
};

export const getServerSideProps: GetServerSideProps = async (context) => {
  const contractAddress = context.params?.contractaddress as string;
  const res = await fetch(`http://localhost:3001/${contractAddress}`);
  const collection = await res.json();
  
  return { props: { collection } };
};

// 适配持久化布局模式
CollectionView.getLayout = function getLayout(page: ReactElement) {
  return page;
};

export default CollectionView;

步骤3:配置路由重写(保持原URL结构)

在next.config.js中添加重写规则,让原URL指向父页面:

module.exports = {
  async rewrites() {
    return [
      {
        source: '/collection/:contractaddress/dashboard',
        destination: '/collection/:contractaddress?view=dashboard',
      },
      {
        source: '/collection/:contractaddress/items',
        destination: '/collection/:contractaddress?view=items',
      },
      {
        source: '/collection/:contractaddress/analytics',
        destination: '/collection/:contractaddress?view=analytics',
      },
    ];
  },
};

这种方式下,用户访问原URL会被重定向到父页面,仅首次加载时获取一次数据,切换视图时不会触发服务端请求,布局状态也能保持持久化。

方案3:结合客户端缓存(SWR/React Query)

若希望客户端导航时完全避免服务端请求,可使用SWR或React Query实现客户端缓存,同时保留首次加载的服务端渲染(SEO友好)。

步骤1:初始化SWR配置

在_app.tsx中配置SWR:

import { SWRConfig } from 'swr';

function MyApp({ Component, pageProps }) {
  const getLayout = Component.getLayout || ((page) => page);
  
  return (
    <SWRConfig
      value={{
        fetcher: (url) => fetch(url).then(res => res.json()),
        revalidateOnFocus: false,
      }}
    >
      {getLayout(<Component {...pageProps} />)}
    </SWRConfig>
  );
}

export default MyApp;

步骤2:修改页面组件使用SWR

以dashboard.tsx为例:

import { useSWR } from 'swr';
import { GetServerSideProps } from "next";
import { NextSeo } from "next-seo";
import type { ReactElement } from "react";
import { CollectionLayout } from "../../../../layouts/CollectionLayout";
import type { NextPageWithLayout } from "../../../_app";

type Props = {
  collection: {
    contractAddress: string;
    name: string;
    description: string;
    image: string;
  };
};

const Dashboard: NextPageWithLayout<Props> = ({ collection: initialData }) => {
  const contractAddress = initialData.contractAddress;
  // 使用服务端返回的初始数据,后续客户端请求复用缓存
  const { data: collection } = useSWR(`http://localhost:3001/${contractAddress}`, {
    initialData,
  });

  return (
    <div className="w-full">
      <NextSeo title={collection.name} description={collection.description} />
      <div className="bg-purple-400 h-[400px] w-[500px]">Dashboard</div>
    </div>
  );
};

export const getServerSideProps: GetServerSideProps = async (context) => {
  const contractAddress = context.params?.contractaddress as string;
  const res = await fetch(`http://localhost:3001/${contractAddress}`);
  const collection = await res.json();
  
  return { props: { collection } };
};

Dashboard.getLayout = function getLayout(page: ReactElement) {
  return (
    <CollectionLayout collection={page.props.collection}>
      {page}
    </CollectionLayout>
  );
};

export default Dashboard;

首次加载时数据由服务端获取(满足SEO需求),后续客户端切换页面时,SWR会复用客户端缓存的数据,无需触发服务端请求。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 03:15:57