Skip to content

Fix flash of unstyled content when loading the docs site - #473

Open
koic wants to merge 1 commit into
modelcontextprotocol:mainfrom
koic:fix_docs_theme_stylesheet_fouc
Open

Fix flash of unstyled content when loading the docs site#473
koic wants to merge 1 commit into
modelcontextprotocol:mainfrom
koic:fix_docs_theme_stylesheet_fouc

Conversation

@koic

@koic koic commented Jul 30, 2026

Copy link
Copy Markdown
Member

Motivation and Context

Opening https://ruby.sdk.modelcontextprotocol.io/ briefly showed a black half-circle at the bottom of the screen. The theme preference script in head_custom.html called window.jtd.setTheme on every page load. That helper rewrites the href of the render-blocking color stylesheet link, which detaches the loaded stylesheet and refetches it, so the page paints unstyled until the replacement arrives. During that window the search magnifier SVG, sized only by CSS, rendered at its fallback size and appeared as a large black half-circle near the bottom of the viewport.

The site sets no color_scheme, so the default stylesheet is identical to the light one and most visitors paid the flash for a no-op swap.

This change replaces the jtd.setTheme call with a local swapper that:

  • Leaves the stylesheet untouched when the resolved theme already matches (treating default as light).
  • Swaps non-destructively otherwise: the new link is inserted next to the current one and the old link is removed only after the new stylesheet has loaded. On error the new link is dropped and the page keeps its current styles.
  • Sets blocking="render" on the new link so supporting browsers hold first paint until the dark stylesheet arrives.

It also pre-applies the dark background and text colors in custom.scss, keyed on the data-theme attribute that is set synchronously in the head, to soften the light-colored first paint for dark-preference visitors in browsers without blocking="render" support.

How Has This Been Tested?

Mirrored the live site locally, patched the inline script with the new code, and drove it in Chrome:

  • Unpatched baseline reproduced the old behavior: the link href was rewritten and just-the-docs-light.css was fetched in addition to just-the-docs-default.css.
  • Patched light and system-light loads keep the original link untouched with no extra stylesheet request.
  • Patched dark loads settle on a single just-the-docs-dark.css link, with jtd.getTheme() still reporting the right theme.
  • Rapid toggling keeps at most two color stylesheet links and settles on exactly one matching the final preference.
  • With just-the-docs-dark.css returning 404, the failed link is dropped and the page stays styled.

Breaking Changes

None.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

## Motivation and Context

Opening https://ruby.sdk.modelcontextprotocol.io/ briefly showed a black half-circle
at the bottom of the screen. The theme preference script in `head_custom.html` called
`window.jtd.setTheme` on every page load. That helper rewrites the `href` of
the render-blocking color stylesheet link, which detaches the loaded stylesheet
and refetches it, so the page paints unstyled until the replacement arrives.
During that window the search magnifier SVG, sized only by CSS, rendered at its fallback size
and appeared as a large black half-circle near the bottom of the viewport.

The site sets no `color_scheme`, so the default stylesheet is identical to the light one
and most visitors paid the flash for a no-op swap.

This change replaces the `jtd.setTheme` call with a local swapper that:

- Leaves the stylesheet untouched when the resolved theme already matches
  (treating `default` as `light`).
- Swaps non-destructively otherwise: the new link is inserted next to the current one
  and the old link is removed only after the new stylesheet has loaded. On error the new link
  is dropped and the page keeps its current styles.
- Sets `blocking="render"` on the new link so supporting browsers hold first paint until
  the dark stylesheet arrives.

It also pre-applies the dark background and text colors in `custom.scss`, keyed on
the `data-theme` attribute that is set synchronously in the head, to soften the light-colored
first paint for dark-preference visitors in browsers without `blocking="render"` support.

## How Has This Been Tested?

Mirrored the live site locally, patched the inline script with the new code,
and drove it in Chrome:

- Unpatched baseline reproduced the old behavior: the link `href` was rewritten
  and `just-the-docs-light.css` was fetched in addition to `just-the-docs-default.css`.
- Patched light and system-light loads keep the original link untouched with no extra stylesheet request.
- Patched dark loads settle on a single `just-the-docs-dark.css` link, with `jtd.getTheme()`
  still reporting the right theme.
- Rapid toggling keeps at most two color stylesheet links and settles on exactly one matching
  the final preference.
- With `just-the-docs-dark.css` returning 404, the failed link is dropped and the page stays styled.

## Breaking Changes

None.
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.

2 participants