[]previous and next page

Images and islands

An image that keeps its box, and parts of a page that become interactive only when they need to.

For agentsUpdated

<Image> takes the intrinsic size of the file. It computes the candidate widths, a lazy or priority load, and the box that keeps the aspect ratio before the file arrives, so the page does not move when the image loads.

src/routes/index.tsxtsx
import { Image } from "@shadow-js/dusk/components";

export default function Home() {
  return (
    <Image src="/hero.jpg" width={1200} height={630} alt="The product on a desk" sizes="100vw" priority />
  );
}

priority loads now with a high fetch priority and preloads the image in the head. fill covers the nearest positioned parent. placeholder="blur" or "color" shows something until the file arrives, and art gives other crops for other screens.

The browser asks /_dusk/image for each candidate. The endpoint serves the app's own files and the origins the config allows, never anything else. Without a processor it serves the source unchanged; give it one to resize and convert. sharpProcessor uses sharp, an optional peer you install in the app.

src/entry-server.tsts
import { sharpProcessor } from "@shadow-js/dusk/server";

export default {
  security: { allowedHosts: ["example.com"] },
  images: { processor: sharpProcessor() },
};

Islands#

<Island> renders its children on the server. The browser takes the island over when its directive fires, with the component the registry names. The props are plain data, so they can be written into the page.

src/routes/index.tsxtsx
import { Island } from "@shadow-js/dusk/components";

export default function Home() {
  return (
    <section>
      <Island id="counter" client:load props={{ start: 3 }}>
        <p>3 items</p>
      </Island>
      <Island id="map" client:visible props={{ center: [51.5, -0.1] }}>
        <p>A map of the offices</p>
      </Island>
      <Island id="editor" client:only fallback={<p>Loading the editor</p>} />
    </section>
  );
}

The directives are client:load, client:idle, client:visible (the default), client:interaction, client:media and client:only, which has no server markup and shows its fallback until the browser mounts the component. The registry names each island, and each entry is a loader, so an island is its own chunk:

src/islands/counter.tsxtsx
export default function Counter(props: { start: number }) {
  return <p>{props.start} items</p>;
}

Next: work offline. <Script>, responsive art direction, remote image origins and the optimizer's limits are in Images, scripts and islands.