Next.js生成上百页面

880 阅读4分钟

需求:

千人千面 例如 : 这是你的官网落地页面:www.abc.com/en/ai-trans…

不同url : /en/audio/aaa-xx /es/audio/bbb-xx /de/transcription/aaa-xx 提高页面曝光

布局是这样的 ↓ ↓ ↓ ↓ 根据500个不同的url同时进入同一个页面 但是大标题小标题及页面SEO 信息跟随所需搜索关键字变化!

image-20240702113127103.png

方案一: 使用nextjs提供的SSG构建时生成数据和页面

实现过程:

将产品给的不同页面展示的title desctiption excel文档 编写了个脚本把格式调整并转为我想要的数据 描述也是为了优化SEO

image-20240702114954417.png

image-20240702115121935.png

app/[locale]/[pathOne]/[pathTwo]/page.tsx 动态路由传参  /en/audio/ai-audio-transcription
// 页面内实现
interface IPageText {
  h1: string,
  subtitle: string,
  docs: {
    title?: string,
    desc?: string,
    list?: Array<{ title: string, desc: string }>
  }
}
const Page_Text: Array<IPageText> = processStringArr();//获取到转换的数据// 页面meta元数据 params 通过 动态路由获取  params= { pathOne,pathTwo }
export async function generateMetadata({ params }: { params: PageParams }): Promise<Metadata> {
   //toKebabCase => ai-audio-transcription => Ai Audio Transctiption
  const original = Page_Text.find(page => toKebabCase(page.h1) === params.pathTwo);
  //找到对应数据然后填充给meta
  return {
    title: original.h1,
    description: original.subtitle,
    alternates: getHrefLangLinks(original.h1, params.locale),
  };
}
​
//SSG使用官网提供generateStaticParams 它是构建时才会运行 生成页面url
// 这里return的内容会在页面组件中获取到
export const generateStaticParams = async () => {
  const paths: { locale: string,pathOne:string,pathTwo:string}[] = [];
  // 官网有9中语言进行遍历生成不同语言的不同静态页面 
  languages.forEach((locale: any) => {
    titleMap.forEach((originalTitle, kebabTitle) => {
      paths.push({ locale, pageTwo: kebabTitle });   // en/audio/ai-audio-text url已经生成
    });
  });
​
  return paths;
}
​
//页面组件
const TranscriptionPageWrapper = ({ params }: { params: PageParams }) => {
  //拿到数据进行渲染页面
  const originalTitle = Page_Text.find(page => toKebabCase(page.h1) === params.title)?.h1;
  const pageText = Page_Text.find(page => page.h1 === originalTitle);
​
  if (!pageText) {
    return <div>Page not found</div>;
  }
​
  return <TranscriptionPage pageText={pageText} />;
};
​

优点:

generateStaticParams构建时页面已经生成访问速度快

缺点:

构建时速度变慢 , 页面数据不易维护(因为需要配合动态路由传参+nextjs 提供的 generateStaticParams ), 最重要一点: 由于内容是获取得到的,所以多语言不会在构建时加载 太慢 速度

方案二: 使用db库方式存储数据并通过SSR方式生成数据展示页面

实现:

在next.config.js中配置rewrites路径

async rewrites() {
​
    return {
    
          {
          source: '/:locale/audio/:path*',
          destination: '/:locale/dsa-page?pathPart1=audio&pathPart2=:path*',
        },
        {
          source: '/:locale/transcription/:path*',
          destination: '/:locale/dsa-page?pathPart1=transcription&pathPart2=:path*',
        },
        {
          source: '/:locale/convert-text/:path*',
          destination: '/:locale/dsa-page?pathPart1=convert-text&pathPart2=:path*',
        },
        {
          source: '/:locale/scenes/:path*',
          destination: '/:locale/dsa-page?pathPart1=scenes&pathPart2=:path*',
        }
    
    }
}

不需要定义动态路由那种文件方式 直接匹配到 /audio/ai-audio-text url不变

页面地址为到/:locale/dsa-page?pathPart1=audio?pathPart2=ai-audio-text

这样直接通过SSR在node端通过获取到的searchparams和locale 去获取对应的 页面内容数据

有个细节值得学习react的cache 缓存请求 因为在 DspPage 和 generateMetadata都需要获取页面的信息 优化请求次数

import React, { cache } from 'react';
import fetchPageInfo from '@/app/api/dsapage';
import { IDspage } from '@/interfaces/transcription'
import TranscriptionPage from '../ai-audio-transcription/page'
import { getHrefLangLinks } from '@/app/i18n/settings';
import { Metadata } from 'next';
interface DspPageProps {
    params: {
        locale: string;
    };
    searchParams: {
        pathPart1: string;
        pathPart2: string;
    };
}
​
​
const fetchPageProps = cache(async (pathone: any, pathtwo: any, locale: string) => {
    return await fetchPageInfo(`${pathone}/${pathtwo}`, locale)
})
​
const getTextValue = (data: any[], type: string) => {
    return data.find((text: any) => text.textType === type).textValue
}
​
export default async function DspPage({ params, searchParams }: DspPageProps) {
    const { locale } = params;
    const { pathPart1, pathPart2 } = searchParams;
​
    const data: { dsaPages: IDspage[] } | null = await fetchPageProps(pathPart1, pathPart2, locale);
​
    const dsaPages: IDspage = data?.dsaPages[0] || {
        path: '',
        texts: []
    };
​
    return <>
        <TranscriptionPage pageText={dsaPages.texts} /> 
    </>
}
​
​
export async function generateMetadata({ params, searchParams }: any): Promise<Metadata> {
​
    const { pathPart1, pathPart2 } = searchParams;
    const data: { dsaPages: IDspage[] } | null = await fetchPageProps(pathPart1, pathPart2, params.locale);
​
    const dsaPages: IDspage = data?.dsaPages[0] || {
        path: '',
        texts: []
    };
​
    return {
        title: getTextValue(dsaPages.texts, 'title') as string,
        description: getTextValue(dsaPages.texts, 'subTitle') as string,
        alternates: getHrefLangLinks(dsaPages.path, params.locale),
    };
}

数据分别建了三张表:pages . textContent.languages

根据接受的url和locale 来获取对应语言的对应内容

image-20240704102527960.png

前端: 请求对应的数据

import serviceAxios from "@/common/ajax";
​
const document = `
  query DsaPages($where: DsaPageWhereInput!, $textsWhere2: TextContentWhereInput!) {
    dsaPages(where: $where) {
      path,
      texts(where: $textsWhere2) {
        textType
        textValue
        language {
          code
        }
      }
    }
  }
`;
​
const fetchPageInfo = async (path: string, language: string) => {
    
  const variables = {
    where: {
      path: {
        equals: path,
      },
    },
    textsWhere2: {
      language: {
        code: {
          equals: language,
        },
      },
    },
  };
​
  try {
    const response = await serviceAxios.post('https://xxxxxx/api/graphql', {
      query: document,
      variables: variables
    });
    
    return response.data;
  } catch (error) {
    throw new Error((error as Error).message as string);
  }
};
​
export default fetchPageInfo;

500个页面的数据需要写个脚本对语言和内容注入到对应表中 ....会有些复杂 但数据管理维护起来比较方便

优点:

SSR会对内容进行缓存 通过rewrit重些到这顶页面并获取searchparams 配置动态generateMetadata

缺点:

线上例如:audio/ai-audio-text单个页面第一个用户首次进入时会加载 后续其他用户进入同一个就会拿缓存来渲染