Skip to content

fix(fonts): self-host branding fonts so no Google Fonts <link> is emitted#321

Merged
mortondev merged 1 commit into
mainfrom
fix/self-host-branding-fonts
Jun 30, 2026
Merged

fix(fonts): self-host branding fonts so no Google Fonts <link> is emitted#321
mortondev merged 1 commit into
mainfrom
fix/self-host-branding-fonts

Conversation

@mortondev

Copy link
Copy Markdown
Member

Problem

The portal, widget, and auth shells render a fonts.googleapis.com stylesheet <link> for the selected branding font (getGoogleFontsUrl). When a deployment sits behind a CDN font optimizer that rewrites HTML at the edge (e.g. Cloudflare Fonts), that <link> is replaced with an inline <style>@font-face ...> block before the browser receives it. The served DOM no longer matches what the client bundle renders, so hydration fails with React #418 on every portal page and the tree is re-rendered on the client.

It only reproduces where the optimizer is enabled, so two deployments on the same version can behave differently.

Fix

Self-host every selectable branding font and stop emitting any Google Fonts link, so there is nothing for an edge optimizer to rewrite.

  • Bundle all 15 FONT_OPTIONS fonts via @fontsource (weights 400-700; Lato ships 400/700) and @import them in globals.css.
  • Remove the Google Fonts plumbing: GOOGLE_FONT_MAP, getGoogleFontsUrl, the portal/widget/auth-shell <link>s, the theme-preview copy, and ALL_FONTS_URL.

Notes

  • Geist ships from @fontsource as the Geist Sans family, so its FONT_OPTIONS value is updated. A workspace that previously stored the bare Geist value falls back to the system sans until branding is re-saved (no data migration).
  • The Scalar API reference page (routes/api/v1/docs.ts) still loads Google Fonts, but it is static non-hydrated HTML so an edge rewrite there cannot cause #418. Left as-is.

Validation

  • bun run build: 307 @font-face, 279 woff2 emitted, 0 fonts.googleapis.com references in the client output.
  • Tests pass; typecheck clean (the pre-existing unrelated mcp/tools.ts zod errors aside).

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d3750b89a8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/web/src/globals.css
@mortondev mortondev force-pushed the fix/self-host-branding-fonts branch from d3750b8 to 39a1544 Compare June 30, 2026 08:25

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 39a1544a40

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/web/src/globals.css
@mortondev mortondev force-pushed the fix/self-host-branding-fonts branch from 39a1544 to e465a56 Compare June 30, 2026 08:44
…tted

The portal/widget rendered a fonts.googleapis.com stylesheet <link> for the
selected branding font. On a Cloudflare zone with Cloudflare Fonts enabled, the
edge rewrites that link into an inline @font-face block, so the served DOM stops
matching the client render and hydration fails (React #418) on every portal page.

Bundle every font referenced by the branding picker (FONT_OPTIONS) and the theme
presets (presets.ts) via @fontsource and remove the Google Fonts plumbing:
GOOGLE_FONT_MAP, getGoogleFontsUrl, the portal/widget/auth-shell links, the
theme-preview copy, and ALL_FONTS_URL. Geist ships as the "Geist Sans" family,
Lato as 400/700, and Nunito is preset-only.

Legacy branding configs saved with the bare "Geist" value are normalised to
"Geist Sans" at read time (normalizeFontSans, applied in generateThemeCSS and the
admin picker) so they keep rendering, no data migration needed.

Pin the web app's zod to 4.3.6: adding the font deps re-resolved the lockfile and
hoisted zod 4.4.3 to the top, which is type-incompatible with the MCP SDK used in
mcp/tools.ts. The exact pin keeps the app on 4.3.6; the TanStack build tooling
keeps 4.4.3.
@mortondev mortondev force-pushed the fix/self-host-branding-fonts branch from e465a56 to a942daa Compare June 30, 2026 09:26
@mortondev mortondev merged commit 456a296 into main Jun 30, 2026
7 checks passed
@mortondev mortondev deleted the fix/self-host-branding-fonts branch June 30, 2026 09:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant