同一套 Astro SSR 跑多个域名,我是怎么处理 Host、D1 和缓存串站的
同一套 Astro SSR 跑多个域名我是怎么处理 Host、D1 和缓存串站的前段时间我在折腾一个 Astro 内容站。最开始想得很简单页面模板都一样只是域名、站名、栏目和配色不同那就让一套代码多绑几个域名省得每个站复制一份项目。真正做起来才发现页面复用反倒是最容易的部分。麻烦的是 Host 怎么识别、Cloudflare 缓存会不会串、Canonical 会不会写到另一个域名以及内容多了以后 Sitemap 怎么拆。现在项目已经跑在 Cloudflare Workers 上目录数据放 D1图片放 R2。我放一个实际页面方便对照本文说的 SSR 输出和链接结构92电影网。这篇不讲完整建站流程只记几个我自己改了不止一次的地方。先把站点差异收进配置不要散落在页面里第一版里我差点在组件里到处写域名判断。写两三个还能看继续加下去首页、详情页和 Sitemap 各有一套判断迟早对不上。后来我把站点相关的东西都收进SiteConfiginterfaceSiteConfig{domain:string;brand:string;routeKey:string;urlKey:string;tagline:string;accent:string;categories:Array{slug:string;label:string;};}域名、品牌名、路由 key、主题色和栏目都是数据。页面组件只关心“当前是哪一个配置”不再关心“当前是哪一个域名”。这个改动不复杂但后面做缓存、SEO 和数据库查询时都省了事因为它们拿到的是同一个site对象。Host 解析放在 Astro Middleware我的处理方式是在请求刚进来时解析 hostname去掉www和端口再到 Map 里查配置。constbyDomainnewMap(sites.map((site)[site.domain,site]));functiongetSiteByDomain(hostname:string){constnormalizedhostname.toLowerCase().split(:)[0].replace(/^www\./,);returnbyDomain.get(normalized);}exportconstonRequestdefineMiddleware(async(context,next){consturlnewURL(context.request.url);constsitegetSiteByDomain(url.hostname);if(!site){returnnewResponse(Unknown site,{status:404,headers:{x-robots-tag:noindex, nofollow,},});}context.locals.sitesite;returnnext();});之后在 Astro 页面里直接读Astro.locals.site。根域名和 HTTP 我统一 301 到https://www。一开始觉得这只是访问习惯问题后来检查页面才意识到如果几个版本都能返回 200Canonical、站内链接和外部抓取很容易出现不一致。if(url.hostnamesite.domain||url.protocol!https:){url.protocolhttps:;url.hostnamewww.${site.domain};returnResponse.redirect(url,301);}Workers 的预览域名我单独放行做测试但会加noindex。生产环境里遇到没有配置过的 Host直接 404不让它套用默认站点。D1 和 R2 的分工尽量简单目录数据在 D1 里保存标题、简介、地区、年份、集数以及图片对象键。图片本身放 R2。wrangler.jsonc中只声明 binding实际项目里的数据库 ID 等信息这里就不贴了{d1_databases:[{binding:DB,database_name:content-catalog}],r2_buckets:[{binding:POSTERS,bucket_name:content-assets}]}页面不直接拼 R2 存储地址而是统一走站内的/media/路由。这样以后换存储方式内容表和页面模板都不用跟着改。图片基本不会变我给/media/一年缓存if(pathname.startsWith(/media/)){returnpublic, max-age31536000, immutable;}HTML 就不能这么干。首页和栏目会更新用短缓存详情页稍长一点搜索页带用户输入直接private, no-store。最容易出问题的其实是缓存键多域名共用一个 Worker 时我最担心的是串站。比如两个域名都请求/如果自己做缓存键时只保留 pathname那么后缓存的页面可能被另一个域名直接拿走。页面看着还能打开但 title、Canonical、品牌名和内链已经全错了这种问题比报错更难发现。我最后没有单独拿 pathname 拼 key而是直接使用原始 RequestconstedgeCacheawaitcaches.open(content-sites-v1);constcachedawaitedgeCache.match(context.request);if(cached){returncached;}constresponseawaitnext();if(response.ok){awaitedgeCache.put(context.request,response.clone());}returnresponse;完整请求 URL 本身带 Host不同域名自然分开。当然也不是所有 GET 都适合进缓存。我排除了搜索页、带 Authorization 的请求、明确要求no-cache的请求以及中转媒体请求。缓存只是加速手段不能反过来改变页面应该有的行为。为了排查方便我还会在响应头里写x-movier-cache: HIT或MISS。浏览器里一眼能看到比凭感觉猜 Worker 到底有没有命中省事。SEO 信息不能写成一套公共常量多域名项目里Canonical 写死是个低级但很实际的错误。我先根据站点配置得到主 OriginfunctionprimaryOrigin(site:SiteConfig){returnhttps://www.${site.domain};}每个页面再用当前路径生成自己的 Canonicalconstcanonical${primaryOrigin(site)}${pathname};首页输出WebSite详情页按内容类型输出TVSeries栏目页输出ItemList。搜索页我设为noindex,follow因为搜索参数组合太多没必要让它们和正式栏目、详情页抢索引。Sitemap 也碰到过同样的问题。内容少时一个 XML 就够了数据多起来后我改成 Sitemap Index固定页面放main.xml详情 URL 每 2000 条拆一个文件。constpagesMath.max(1,Math.ceil(total/2000));constentries[${origin}/sitemaps/main.xml,...Array.from({length:pages},(_,index)${origin}/sitemaps/items-${index1}.xml,),];这里的origin一定来自当前 Host 对应的站点配置。否则 Sitemap 能正常打开里面却可能混着另一个域名检查不仔细很难发现。我现在上线前怎么检查我不会只看首页能不能打开而是同时开两个不同域名请求相同路径对照 title、Canonical、内链和缓存响应头。然后再看根域名是否只跳一次、搜索页有没有进缓存、预览域名有没有noindex以及 Sitemap 里有没有混入其他 Host。另一个容易漏掉的情况是空栏目。数据库暂时没有匹配内容时与其照常输出一个可以索引的空页面不如先设成noindex,follow也不要放进 Sitemap。等有真实内容再开放索引。回头看这个项目最花时间的地方并不是 Astro 组件而是保证“当前站点身份”从请求进来到页面输出始终一致。Host 解析、D1 查询、缓存键、Canonical 和 Sitemap 只要有一处用了另一套判断线上就会出现很别扭的问题。目前这套方案跑下来比较稳定。以后再加栏目或改页面我只动公共组件和站点配置不用维护很多份差不多的代码。对于内容结构接近、又确实需要独立域名和独立 SEO 信息的项目这种做法还是挺省心的。

相关新闻

完整指南:D2DX暗黑破坏神2高清补丁的三大核心功能与快速安装教程

完整指南:D2DX暗黑破坏神2高清补丁的三大核心功能与快速安装教程

完整指南:D2DX暗黑破坏神2高清补丁的三大核心功能与快速安装教程 【免费下载链接】d2dx D2DX is a complete solution to make Diablo II run well on modern PCs, with high fps and better resolutions. 项目地址: https://gitcode.com/gh_mirrors/d2/d2dx …

2026/10/4 3:59:22 阅读更多 →
《仙剑奇侠传》游戏资源修改指南:零基础实现图片、文本与数值定制

《仙剑奇侠传》游戏资源修改指南:零基础实现图片、文本与数值定制

1. 先搞清楚“不会编程也能改”到底指的是什么看到这个标题,很多人第一反应可能是“不用写代码就能修改游戏内容”。但这里的“改”更准确地说,是指对现有游戏资源进行替换、调整或简单定制,而不是从零开发新功能。这类工具通常面向的是对游戏…

2026/10/10 16:14:06 阅读更多 →
如何高效部署智能求职自动化系统:全平台简历自动投递实战指南

如何高效部署智能求职自动化系统:全平台简历自动投递实战指南

如何高效部署智能求职自动化系统:全平台简历自动投递实战指南 【免费下载链接】get_jobs 💼【AI找工作助手】全平台自动投简历脚本:(boss、前程无忧、猎聘、智联招聘) 项目地址: https://gitcode.com/gh_mirrors/ge/get_jobs 还在为每…

2026/10/3 1:30:25 阅读更多 →

最新新闻

C++内存模型精讲:从内存区域到多线程并发语义

C++内存模型精讲:从内存区域到多线程并发语义

面试现场被问到“C内存模型”或者“内存区域”这类题目时,我见过太多候选人第一反应就是开始背栈、堆、全局区、常量区、代码区。背完之后,面试官如果追问一句“那内存模型和内存区域有什么区别”,现场通常会安静两三秒,然后就开始…

2026/10/11 7:07:38 阅读更多 →
LaTeX 从入门到实战:环境配置、公式排版与模板使用全攻略

LaTeX 从入门到实战:环境配置、公式排版与模板使用全攻略

1. LaTeX 的定位:不只是排版工具,而是写作流程的重构1.1 LaTeX 2e 是什么,它到底解决了什么问题我最早接触 LaTeX 2e 是大三写课程论文的时候。当时 Word 里公式编号乱跳、图片到处跑,一篇三十页的论文排版就花掉三天。后来导师丢…

2026/10/11 7:07:38 阅读更多 →
Spring Boot医疗预约系统设计与实现:从需求到答辩完整路线图

Spring Boot医疗预约系统设计与实现:从需求到答辩完整路线图

每年三四月份,我都会在后台收到一批同款私信:Spring Boot的毕设题目还有没有?有没有医疗预约类的?其实不用多问,你去毕设题库搜一下“Spring Boot”,翻不了几页,“会员制医疗预约服务管理信息系…

2026/10/11 7:07:38 阅读更多 →
团队协同与沟通能力好的6款一站式AI智能办公平台横向对比与选型指南

团队协同与沟通能力好的6款一站式AI智能办公平台横向对比与选型指南

需要团队协同与沟通能力都比较强的一站式 AI 智能办公平台,候选大致分五类:企业级基座、中小团队套件、政企安全协同、AI 轻办公与门户型 OA。本文按同一口径对比 6 款产品:快鹭办公、Worktile、360织语、蓝信、360纳米Work、万户软件&#x…

2026/10/11 7:07:38 阅读更多 →
Python爬虫实战:抓取广东省租房数据并存入SQLite

Python爬虫实战:抓取广东省租房数据并存入SQLite

1. 做这件事之前,先想清楚这几个问题我去年有一阵子想找个合适的住处,顺手查了广东省几个城市的租房行情,结果发现多数平台要么数据藏在交互式页面上,要么只给你看前10页,翻到最后也凑不齐一个像样的统计样本。后来我干…

2026/10/11 7:07:38 阅读更多 →
artcraft:一种可控的创意生产方法论

artcraft:一种可控的创意生产方法论

1. 项目概述:什么是“artcraft”?它不是艺术展,也不是手作市集,而是一套可落地的创意生产方法论“artcraft”这个词最近在设计圈、独立开发者社区和高校创意工坊里频繁出现,但它既不是某个新发布的软件,也不…

2026/10/11 7:06:37 阅读更多 →

日新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 5:23:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 10:38:42 阅读更多 →