需求:
千人千面 例如 : 这是你的官网落地页面:www.abc.com/en/ai-trans…
不同url : /en/audio/aaa-xx /es/audio/bbb-xx /de/transcription/aaa-xx 提高页面曝光
布局是这样的 ↓ ↓ ↓ ↓ 根据500个不同的url同时进入同一个页面 但是大标题小标题及页面SEO 信息跟随所需搜索关键字变化!
方案一: 使用nextjs提供的SSG构建时生成数据和页面
实现过程:
将产品给的不同页面展示的title desctiption excel文档 编写了个脚本把格式调整并转为我想要的数据 描述也是为了优化SEO
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 来获取对应语言的对应内容
前端: 请求对应的数据
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单个页面第一个用户首次进入时会加载 后续其他用户进入同一个就会拿缓存来渲染