fix(docs): collapse the metadataBase hostname into SITE_URL, record the middleware decision - #204
Merged
Conversation
…he middleware decision `app/layout.tsx` now derives `metadataBase` from `SITE_URL` instead of repeating the host, which takes the canonical-host literals in `apps/docs` from three to two. `middleware.ts` keeps its own copy, deliberately. Measured on this tree: importing `SITE_URL` from `lib/seo.ts` pulls the fumadocs loader and every compiled MDX module into the edge bundle, taking it from 149 KB to 14.7 MB of JavaScript with `next build` still exiting 0. The reason is now recorded on both sides, replacing the "keep in sync" instruction on `lib/seo.ts`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G5zjYc2BoFV2NjKBBapC7C
hotlong
marked this pull request as ready for review
August 26, 2026 03:05
This was referenced Aug 26, 2026
hotlong
added a commit
that referenced
this pull request
Aug 26, 2026
…228) The Host header carries host[:port], so comparing it whole against a bare hostname meant www.objectos.app:8080 was never equal to LEGACY_HOST. It fell through to locale negotiation and was served under the legacy host (200) instead of redirected off it (308). Three lines below, the same block already stripped the port from the target, so the port was accounted for on the way out and not on the way in. Compares the hostname now. The target-side normalisation is unchanged. Verified with the redirect table established by #204, #223, #225 and #227, plus two rows: the ported legacy host now 308s with no port on the target, and Host: docs.objectos.ai:3000 stays 200 on both sides -- which proves the comparison is against LEGACY_HOST specifically rather than a blanket port strip, a fix that would have passed every other row. The before column was reproduced by rebuilding and running the pre-fix file, not inferred. Closes #226
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #199
Two halves, deliberately different jobs. The first is the one-line import the card expected. The second is answered with a measurement, and lands as a documented decision.
All readings below are from
3f9394b, the head of this branch.1.
app/layout.tsx— it really was the one-line importmetadataBasenow derives fromSITE_URLinstead of repeating the host. Confirmed rather than assumed:layout.tsxis a pass-through since #168, but it still owns the locale-independent metadata, andlib/seo.tsis reachable from it with no cycle (seoimportssource;sourcedoes not importseo— that is whySITE_NAMElives insource.ts).Nothing else was needed, so the card does not need re-scoping.
2.
middleware.ts— the edge-runtime claim, measuredThe #178 dev's claim came from reading the module graph, not from a build. I built it both ways.
Probe: point
middleware.tsatSITE_URLfrom@/lib/seoand build.@/lib/seotoFumadocsSourcein edge bundlefrontmatterentries inlinedreact-domchunk in edge bundlenext buildexitThe claim holds, and the last row is the part that matters: the build succeeds. Nothing in CI would report a 14.7 MB edge bundle. This app deploys to Cloudflare Workers (
deploy-docs.ymlrunsopennextjs-cloudflare deploy), so the cost would land at deploy time — exactly the silent-in-production failure mode the card warns about.The probe was reverted; its restore is proven by blob identity against
HEAD, not by an exit code.Decision: middleware keeps its own literal, as the card's second acceptable outcome. The redirect answers "which host is canonical" for every request before any page code runs, and that answer should not depend on the module graph that builds pages.
What changed is that the decision is now in the code on both sides:
lib/seo.tsno longer says "Keep in sync with middleware's domain redirect." That instruction to a human was the unenforced-sync shape the card objected to. It now states the reason for the duplication, including the measurement and the fact that nothing enforces agreement.middleware.tshoistsCANONICAL_HOSTandLEGACY_HOSTto named constants carrying the other half of the reason, so the copy is visible as a decision rather than as a stray literal mid-function.Neither side issues an instruction. Both state why.
The residual, stated plainly
Nothing enforces that the two values agree. Collapsing them properly needs a leaf module both runtimes can import (a small
lib/site.tsholding just the host, imported bylib/seo.tsandmiddleware.ts) — that is a new file, outside the three-file surface this card granted, so it is not done here. It is a real option and cheap; it is the maintainer's call, not mine.Verification
The redirect, exercised against a running build (
next start, not read off the source):Host: www.objectos.app/docs/architecture308→https://docs.objectos.ai/docs/architectureHost: www.objectos.app/308→https://docs.objectos.ai/Host: www.objectos.app/docs/architecture?q=1308→https://docs.objectos.ai/docs/architecture?q=1(query kept)Host: www.objectos.app/zh-Hans/docs/architecture308→https://docs.objectos.ai/zh-Hans/docs/architectureHost: docs.objectos.ai/docs/architecture200(control: canonical host not redirected away)Host: docs.objectos.ai/cn/docs/architecture308→/zh-Hans/docs/architecture(control: legacy locale redirect intact)Rendered metadata is unchanged. A raw byte diff of
.nextis useless here — Next salts every HTML and RSC payload with a fresh build ID, so all 6,152 prerendered outputs differ between any two builds. Instead I extracted every URL-bearing head tag (canonical,og:*,twitter:*,hreflangalternates, icons, title) from all 577 prerendered pages and compared pre-change against post-change:Spot check on a fallback-locale page, which #166 and #174 made non-trivial —
/zh-Hans/docs/architecturecorrectly points at the English canonical:Gates, all at
3f9394b, each read from the command's own verdict line with the exit code captured before any pipe:pnpm turbo run type-check --continueTasks: 1 successful, 1 totalpnpm turbo run buildCompiled successfully in 36.3spnpm turbo run testTasks: 1 successful, 1 totalnode .github/scripts/check-node-floor.mjsnode .github/scripts/check-locale-surface.mjsHostname literals in
apps/docsafter this changeCanonical docs host: 3 → 2.
lib/seo.ts:21SITE_URLapp/layout.tsxnow derivesmetadataBasefrom itmiddleware.ts:26CANONICAL_HOSTThree further hostname literals exist in
apps/docs, none of them duplicates — each is the only copy of a distinct host:middleware.ts:27LEGACY_HOSTwww.objectos.applib/layout.shared.tsx:10WEBSITE_URLwww.objectos.ainext.config.mjs:27objectstack.airemotePatternsallowlist entryProse mentions in
app/[lang]/privacy/page.tsx,app/[lang]/terms/page.tsxand a historical note inapp/llms.txt/route.tsare reader-facing text, not configuration, and were left alone.Scope
Three files, exactly the granted surface.
lib/seo.tsgained no export and lost none — only the comment aboveSITE_URLchanged — so thelib/source.tsread coupling with #197 is untouched. No changeset (this repo has no changeset flow).Generated by Claude Code
Generated by Claude Code