Next.js + Vite,这是什么新的操作?
该渲染由 Shiro API 生成,可能存在排版问题,最佳体验请前往:https://innei.in/posts/Z-Turn/nextjs%2Bvite-hack-combined
事情是这样的。
前段日子做了一个摄影佬必备的线上图库。
https://github.com/Afilmory/Afilmory
这个项目是 Vite + React 写的,当初没有考虑要做 SEO 的打算。
然后想给 /:photoId
路由加个 Open Gragh 的支持。那必须得上一个 Server 了。
那用什么呢,不管用什么必须的支持 Serverless,毕竟我也不想多开一个服务器去托管了。
现在支持 Serverless 的太多,hono,fastify 都可以。我最后还是选择了 Next.js。一开始只想着它处理和画 OG 最方便,而且可以直接托管到 Cloudflare Pages 上。
目前,已知我的 app 是 SPA,vite build 之后是静态产物。我需要在 Next.js 上托管这些静态产物。
托管静态产物,这还不简单。我直接一个 vite build && cp -r dist ../ssr/public
。放到 public
下就被 Next.js 自动托管了。
但是,index.html 的 <head />
咋办,vite build 出来的都是死的。
我直接用 dom 操作替换一下。
import { DOMParser } from 'linkedom'
export const runtime = 'edge'
export const GET = async (
request: NextRequest,
{ params }: { params: Promise<{ photoId: string }> },
)
try {
const indexHtml = await fetch(new URL('./index.html', import.meta.url)).then(r => r.text())
const document = new DOMParser().parseFromString(indexHtml, 'text/html')
// Remove all twitter meta tags and open graph meta tags
document.head.childNodes.forEach((node) => {
if (node.nodeName === 'META') {
const $meta = node as HTMLMetaElement
if ($meta.getAttribute('name')?.startsWith('twitter:')) {
$meta.remove()
}
if ($meta.getAttribute('property')?.startsWith('og:')) {
$meta.remove()
}
}
})
// Insert meta open graph tags and twitter meta tags
createAndInsertOpenGraphMeta(document, photo, request)
return new Response(document.documentElement.outerHTML, {
headers: {
'Content-Type': 'text/html',
'X-SSR': '1',
},
})
} catch (error) {
console.error('Error generating SSR page:', error)
console.info('Falling back to static index.html')
console.info(error.message)
return new Response(indexHtml, {
headers: { 'Content-Type': 'text/html' },
status: 500,
})
}
}
那么就搞定了对 HTML 的处理,注入了 OG 相关的 Meta 标签。
这里有个注意,截止到这边文章编写前,在使用 Cloudflare Pages 部署 Next.js app 仍有不少问题。比如这里的 fetch 可能导致报错 Cannot perform Construct on a detached ArrayBuffer。我现在的方案就是不适用 fetch。而是在 build 阶段转换为 js 文件。
# Convert HTML to JS format with exported string
node -e "
const fs = require('fs');
const html = fs.readFileSync('./public/index.html', 'utf8');
const jsContent = \`export default \\\`\${html.replace(/\`/g, '\\\\\`').replace(/\\\$/g, '\\\\\$')}\\\`;\`;
fs.writeFileSync('./src/index.html.ts', jsContent);
"
OK,这样就搞定了。后面开发还是可以用 SPA 去开发,部署的时候去部署 Next.js。直接通过 Cloudflare Pages 一键部署就行了。
后续
Cloudflare Pages 免费版本的 Worker CPU Time 限制的太低了,而生成 OG 的事件远超这个需要的时间,导致 OG 生成经常不可用。现在使用 Railway 去部署了。