Skip to content

custom domains: personalization#2961

Draft
Soxasora wants to merge 22 commits into
stackernews:masterfrom
Soxasora:feat/custom-domains-personalization
Draft

custom domains: personalization#2961
Soxasora wants to merge 22 commits into
stackernews:masterfrom
Soxasora:feat/custom-domains-personalization

Conversation

@Soxasora
Copy link
Copy Markdown
Member

@Soxasora Soxasora commented May 10, 2026

schema and resolver not final, in dev

Description

Part of #1942, revives and rewrites #2202
Adds basic theming and SEO capabilities to territories with custom domains.
Theme and SEO data are then used to power dynamic RSS and PWA.

DomainSeo, tied to a custom domain

  • title
  • tagline (meta description)
  • favicon

SubTheme, tied to a territory

  • primaryColor
  • secondaryColor
  • linkColor
  • logo

This split allows us to re-use territory themes on stacker.news if in the future we decide so.

Screenshots

Territory theme and Domain SEO forms

image

Custom domain with theme and seo

image

PWA installation (dynamic site.webmanifest is recognized)

Screen.Recording.2026-05-12.at.13.01.34.mov

Dynamic RSS with external territories support

image

Additional Context

The custom territory theme CSS is built by buildSubThemeCSS and injected into _document to ensure that the very first paint is already themed with the territory theme.

buildSubThemeCss turns SubTheme into a string of CSS variable overrides:

  • --bs-primary(-rgb)
    • --theme-primary-text
  • --bs-secondary(-rgb)
    • --theme-secondary-text
  • --theme-link(Hover)

The *-text values are a YIQ contrast pick (#000 or #fff) computed by getContrastTextColor(), because the foreground color on top of a brand color cannot be derived in CSS. Bootstrap handles color mixing and contrast calculations internally, forcing us to essentially re-do what it natively does in order to control the colors.

The output CSS features a doubled selector, raising specificity so the overrides win against the scss :root defaults.

Bootstrap buttons' button-variant() mixin bakes literal hex values into --bs-btn-bg, -bs-btn-hover-bg, ..., and the .btn base class reads those values instead of --bs-primary.
As a workaround, following Bootstrap's directions, we now:

  • rebind the per-component vars (.btn-primary, .btn-secondary, ...) so --bs-btn-bg reads the primary color and --bs-btn-color reads --theme-primary-text
  • derive hover and active variants with color-mix, which is the runtime equivalent of scss' shade-color()
  • SVG glyphs inside primary and secondary buttons follow the --bs-btn-color updated earlier.

SN default theme is exactly the same as before and now territory theme colors can affect buttons.

twbs/bootstrap#38788 (comment)
twbs/bootstrap#38170

Checklist

Are your changes backward compatible? Please answer below:

Yes!

On a scale of 1-10 how well and how have you QA'd this change and any features it might affect? Please answer below:

6, did not test edge cases

  • RSS: OK
  • PWA: OK
  • SubTheme and DomainSeo forms: OK
  • Apollo merge: OK
  • Bootstrap overrides: OK

For frontend changes: Tested on mobile, light and dark mode? Please answer below:
in QA

Did you introduce any new environment variables? If so, call them out explicitly here:
n/a

Did you use AI for this? If so, how much did it assist you?
Yes, initial shape and working prototype. This PR takes inspiration from the vibe-shaped prototype.

Soxasora and others added 22 commits May 10, 2026 12:05
DomainSeo handles title, tagline, favicon and og:image (social preview); SubTheme handles logo, colors (primary, secondary, link) and the default color scheme (system/dark/light)
…A_URL for uploaded brand assets

- useDomainSeo allows custom SEO only if we're visiting a custom domain (and the territory has custom SEO)
…rst paint flickers

- color utilities that normalize hex and rgb colors
- buildSubThemeCss helper, overrides globals.scss and bootstrap colors
Comment thread lib/safe-url.js
//
// Accepts either an edge NextRequest, a Node IncomingMessage, or undefined.
// When called in the browser with no req, derives from window.location.
export function isSecureRequest (req) {
Copy link
Copy Markdown
Member Author

@Soxasora Soxasora May 12, 2026

Choose a reason for hiding this comment

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

I moved isSecureRequest from /lib/auth.js to /lib/safe-url.js to decouple it from auth. Hope it's okay!

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