island-server-frame
"An island's `serverFrame` declaration must match what it actually server-renders."
Venue: check — An island’s
serverFramedeclaration must match what it actually server-renders.
An island may declare that it has no server-rendered first frame:
export const editor = { live: true };export const serverFrame = false; // nothing to render until a browser draws itDeclaring it removes the island’s implementation — and everything it imports — from the section’s renderer bundle. The server emits the island’s marker and nothing inside it; the browser mounts the island fresh instead of hydrating.
check renders every section’s fixtures with its islands marked, so it can see what each
island actually produced, and holds the declaration to it:
| What the island declared | What it rendered | What check does |
|---|---|---|
serverFrame = false |
real markup | fails — that markup is being deleted from published pages |
| nothing | an empty element | warns, naming the bytes the declaration would drop |
| anything else | anything | nothing to say |
“Real markup” means content a visitor or a crawler gets with no JavaScript: text, or an
element that is content standing empty — an <img>, <svg>, <video>, <iframe>, a
form control. An empty <div> is not; neither is a <canvas>. That is the whole
distinction the declaration turns on.
Reason
Section titled “Reason”The two directions are asymmetric on purpose, because the evidence behind them is.
The failure is content deletion. A renderer build acts on the declaration: it never loads the module, so whatever the component would have returned is simply absent from the HTML every published page serves. Nothing else catches this. The section still renders, the island still hydrates in a browser, and the page looks right to the author who wrote it — while a crawler, a visitor with JavaScript off, and the page’s own layout before hydration all see a hole. Since the declaration is the thing that caused the deletion, grading it against a real render is the only place the mistake is visible.
The warning is advice, and cannot be more than that. Fixtures are one set of props among all the props a section can be filled with. An island that renders nothing for the fixtures you wrote may render plenty for a prop shape none of them reaches, and that island is correct as written. Refusing it would make the fixture corpus, rather than the contract, the thing you author against. So the empty direction reports what it saw, names what the declaration would be worth, and leaves the decision with you.
If it failed: delete the declaration. This island renders content the server can produce, and something downstream depends on it.
If it warned: decide which of two things is true.
- The island’s content genuinely does not exist until a browser draws it — a map engine painting into a canvas, a chart measuring its container. Declare it, and the renderer stops carrying the library.
- The island does render content, just not for the props these fixtures supply. Leave it undeclared. Nothing is refused, and you can silence the report by adding a fixture that exercises the case if it is worth having one.
An island declaring serverFrame = false must also be live: true in the editor (see
require-island-editor): an island with no server frame that
also does not hydrate in the canvas is a permanent blank hole.
Before
Section titled “Before”"use client";
import { useState } from "react";
export const editor = { live: true };export const serverFrame = false; // ← but this component's rows ARE its content
export default function AmenityList({ amenities }: { amenities: string[] }) { const [open, setOpen] = useState(false); const shown = open ? amenities : amenities.slice(0, 3);
return ( <ul> {shown.map((amenity) => ( <li key={amenity}>{amenity}</li> ))} <li> <button type="button" onClick={() => { setOpen(!open); }}> {open ? "Show fewer" : "Show all"} </button> </li> </ul> );}"use client";
import { useState } from "react";
export const editor = { live: true };// No serverFrame declaration: the rows below are this island's first frame, and the// server renders them.
export default function AmenityList({ amenities }: { amenities: string[] }) { const [open, setOpen] = useState(false); const shown = open ? amenities : amenities.slice(0, 3);
return ( <ul> {shown.map((amenity) => ( <li key={amenity}>{amenity}</li> ))} <li> <button type="button" onClick={() => { setOpen(!open); }}> {open ? "Show fewer" : "Show all"} </button> </li> </ul> );}See also
Section titled “See also”require-island-editor— the other declaration every island carries, and the oneserverFrame = falseconstrains.size-renderer-bundle— the budget a frameless island’s graph is dropped from.