refactor(docs): give the canonical host one definition in a leaf module - #227
Merged
Conversation
`SITE_URL` in `lib/seo.ts` and `CANONICAL_HOST` in `middleware.ts` were two literals for one value with nothing enforcing they agreed. `lib/site.ts` now holds the host and nothing else; `lib/seo.ts` derives the origin from it and `middleware.ts` redirects to it. The leaf constraint is the point: `middleware.ts` runs in the edge runtime and `lib/seo.ts` reaches `lib/source.ts`, so importing the host from there pulls the whole MDX collection into the edge bundle while `next build` still exits 0. `lib/site.ts` imports nothing, and each of the three files says why. 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 10:12
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 #205
SITE_URLinlib/seo.tsandCANONICAL_HOSTinmiddleware.tswere two literals for one value.apps/docs/lib/site.tsnow holds the host and nothing else;lib/seo.tsderives the origin from it,middleware.tsredirects to it.app/layout.tsxneeded no change — it takesSITE_URLfromlib/seo.ts, which is still exported from there, just no longer written out there.All measurements below are at
98ecc0e, the branch head.The size, which is the acceptance criterion
Edge runtime JS = the sum of the JS chunks the middleware entry names in
.next/server/middleware-manifest.json. The route tablenext buildprints reports no size for middleware, so there is nothing to read there instead.next build0c4351f98ecc0emiddleware.tsimporting@/lib/seoZero growth — and stronger than equal totals: the three chunk files are byte-for-byte identical to the baseline, same content-hashed names, same sizes (
[root-of-the-server]__07_86ae._.jsat 139,218 B in both). The import adds no module-wrapper overhead at all; the bundler inlines the constant exactly as it inlined the local one.The baseline was re-measured rather than assumed. #205 recorded 149,491 B; the current figure is 149,745 B, 254 B higher, consistent with #223 and #225 having landed since.
The control row is the noise floor. Two builds of unmodified source produced the identical byte count, so the 0 B delta on this branch is a measurement and not a coincidence.
The ablation row was measured on this tree, not quoted. #205 carried 14,849,913 B from #204's run; on today's content, with zh-Hant shipped, pointing
middleware.tsat@/lib/seoproduces 17,375,914 B — 116x, not 99x, and growing with the collection.next buildexited 0, as advertised. The mutation was confirmed on disk before the build (grep counts on both the injected and the removed text) and the restore was verified bygit hash-objectagainst the HEAD blob, withgit diff HEADempty afterwards.apps/docs/lib/site.tsimports nothing. It is one exported string and a comment explaining why it must stay that way.Rendered metadata unchanged
#204's method: every URL-bearing
link/metatag in the head of all 659 prerendered HTML files, plus every absolute URL inrobots.txt,sitemap.xml,llms.txtandllms-full.txt— 12,696 rows, sorted, diffed.No byte diff of
.nextwas attempted; Next salts every build, so that comparison says nothing.The domain redirect
Six cases against
next starton the branch build, plus four controls for what #223 and #225 established. Server killed by explicit PID after confirming its cwd was inside this worktree.www.objectos.app/docs/quickstarthttps://docs.objectos.ai/docs/quickstartwww.objectos.app/docs/quickstart?utm_source=x&q=a%20bhttps://docs.objectos.ai/docs/quickstart?utm_source=x&q=a+bwww.objectos.app/https://docs.objectos.ai/docs.objectos.ai/docs/quickstart/cn/docs/quickstart/zh-Hans/docs/quickstartwww.objectos.app/cn/docs/quickstarthttps://docs.objectos.ai/cn/docs/quickstart/zh-Hant/docs/quickstart/zh-hant/docs/quickstart/zh-Hant/docs/quickstart/en/docs/quickstart/docs/quickstartCase 2 preserves the query. Case 6 is the ordering control: the domain rule runs before the legacy-locale rule, so the host is corrected first and
/cn/is handled on the next hop. Cases 7 to 9 are #225's folded-index behaviour, untouched.One definition is now an enforced definition
The card was filed because nothing failed if the two values drifted. They can no longer drift — there is one of them — and moving that one is caught by a gate that already exists.
.github/scripts/check-locale-surface.mjskeeps its own copy of the host on purpose, documented in place as the independent oracle: "a check that shares a constant with the thing it checks cannot catch that constant being wrong." It compares the builtsitemap.xmlagainst URLs it constructs from its own literal.sitemap.tsbuilds its URLs throughlib/seo.ts, which now derives fromSITE_HOST— so the gate transitively pins the middleware's redirect host as well, which it could not do before.Measured, not asserted. Setting
SITE_HOSTtodocs.objectos.invalidand rebuilding:Restored and verified by hash against the HEAD blob. As a side observation, that run put the edge bundle at 149,750 B — exactly 5 B more, the length difference between the two hostnames. The host string is inlined into the edge chunk and the size tracks it byte for byte, which is independent confirmation that nothing but the string crosses the import.
Gates
All at
98ecc0e, all with--forcebecause the turbo cache is shared across worktrees in this container, and all with the exit code captured before any pipe.turbo run build --force --filter=@objectos/docsturbo run type-check --force --filter=@objectos/docsturbo run test --forcenode .github/scripts/check-locale-surface.mjssitemap.xml409 URLs, 0 unexpected, 0 missingNo changeset: this repo has no
.changesetdirectory and no changeset workflow.Out of scope, filed
www.objectos.app:8080skips the canonical redirect while the same block clears the port on the target side. Found while running the table above. Low severity and possibly zero in production; filed unassigned for triage, not touched here.Generated by Claude Code