Diagnostics

LUM4004: Browser API at module scope

A module that runs on the server uses a browser API at module scope.

Severity: warning · Category: islands and boundaries (4xxx)

Reported by: the compiler, for .stsx/.tsx modules: @lumiose/vite when it compiles a module for the server (lumiose dev and lumiose build print the warning), and lumiose check and the language server for every module except client entries (modules that import lumiose/dom) and spa routes with export const ssr = false. A warning.

#What it means

Components are imported on the server for SSR. window, document, localStorage and similar globals do not exist there, so touching them while the module loads crashes the server render.

#Example

tsx
const saved = localStorage.getItem("theme");

export function ThemeToggle() {
  return <button>{saved}</button>;
}

Reported: window, document, localStorage, sessionStorage, navigator, location, history, matchMedia, requestAnimationFrame, customElements, IntersectionObserver and similar browser-only globals, when they are evaluated at module scope. Not reported: code inside a function (a component, an effect, an event handler, onMount) or a class, a typeof window check, code guarded by one (typeof window !== "undefined" && …, if (typeof document === "object") { … }), and a local or imported binding with the same name. Plain .ts modules are not analysed.

#How to fix it

Read browser APIs inside onMount, an effect or an event handler, which only run in the browser:

tsx
export function ThemeToggle() {
  const theme = signal("light");
  onMount(() => theme.set(localStorage.getItem("theme") ?? "light"));
  return <button>{theme()}</button>;
}