Skip to content
Astro as islands 4 min read

client:only

The client:only directive tells Astro to skip server rendering entirely for a UI framework component and render it exclusively in the browser. Unlike the other client:* directives, which send pre-rendered HTML and then hydrate it, client:only sends no markup for the component at all — the framework boots up on the client and produces the DOM from scratch. This is the escape hatch for components that simply cannot run on the server.

When you need client:only

Some components reference browser-only APIs at render time — window, document, localStorage, the Canvas or WebGL context, or libraries that touch the DOM during their initial render (charting, mapping, rich-text editors). Server-rendering those components throws because those globals don’t exist in Node. client:only sidesteps the problem by never rendering on the server.

---
// src/pages/dashboard.astro
import LiveChart from "../components/LiveChart.jsx";
---
<h1>Analytics</h1>
<LiveChart client:only="react" />

Because nothing is rendered on the server, you must explicitly tell Astro which framework runtime to load — Astro cannot infer it from server output the way it does for client:load or client:visible.

Warning: client:only produces a content layout shift and a flash of empty space until the JS loads and runs. Prefer a hydrating directive (client:load, client:visible) whenever the component can render on the server, and reserve client:only for components that genuinely cannot.

Specifying the framework

The value passed to client:only is the name of the renderer integration, not the file extension. Use the matching string for whichever framework the component is written in:

FrameworkDirective value
Reactclient:only="react"
Preactclient:only="preact"
Vueclient:only="vue"
Svelteclient:only="svelte"
SolidJSclient:only="solid-js"
---
import MapWidget from "../components/MapWidget.vue";
import Editor from "../components/Editor.svelte";
---
<MapWidget client:only="vue" />
<Editor client:only="svelte" />

If you omit the value, Astro cannot determine the runtime to ship and will fail the build. The corresponding integration must also be registered in astro.config.mjs:

// astro.config.mjs
import { defineConfig } from "astro/config";
import react from "@astrojs/react";
import vue from "@astrojs/vue";

export default defineConfig({
  integrations: [react(), vue()],
});

Handling the loading gap

Since the component renders nothing on the server, you typically want a placeholder so the layout doesn’t jump. Astro provides a slot fallback pattern using the component’s own framework, but the simplest reliable approach is to reserve space and let the component manage its own loading state once it mounts.

---
import LiveChart from "../components/LiveChart.jsx";
---
<div class="chart-slot" style="min-height: 320px;">
  <LiveChart client:only="react" />
</div>

Inside the component, render a skeleton until the browser-only dependency is ready:

// src/components/LiveChart.jsx
import { useEffect, useState } from "react";

export default function LiveChart() {
  const [data, setData] = useState(null);

  useEffect(() => {
    // window/localStorage are safe here — this only runs in the browser
    const cached = window.localStorage.getItem("metrics");
    if (cached) setData(JSON.parse(cached));
    else fetch("/api/metrics").then((r) => r.json()).then(setData);
  }, []);

  if (!data) return <p>Loading chart…</p>;
  return <pre>{JSON.stringify(data, null, 2)}</pre>;
}

Output:

Loading chart…   ← shown until JS loads and effect runs
{                ← replaced once data resolves
  "visits": 1280,
  "signups": 42
}

How it differs from hydrating directives

The key distinction is what the server sends. With a hydrating directive, the server emits real HTML and the client “takes over” that markup. With client:only, the server emits a placeholder marker only.

Behaviorclient:onlyclient:load
Server-rendered HTMLNoneYes
Framework value requiredYesNo
Works without browser globals at renderN/A (never runs on server)Must be SSR-safe
First paint shows contentNo (empty until JS)Yes (immediately)
SEO crawlable contentNoYes

Tip: Because client:only content is not in the server HTML, search engines and no-JS users see nothing. Never put SEO-critical or above-the-fold core content behind client:only.

Best Practices

  • Use client:only only for components that break during SSR; everything else should hydrate so users get instant HTML.
  • Always pass the explicit framework value ("react", "vue", "svelte", etc.) — omitting it breaks the build.
  • Register the matching framework integration in astro.config.mjs before using its directive value.
  • Reserve layout space with a min-height wrapper or in-component skeleton to avoid content layout shift.
  • Keep client:only islands small and isolated so the JS-only region stays narrow and the rest of the page remains zero-JS.
  • Move data fetching and window/document access into effects or lifecycle hooks so the logic only runs client-side, where it’s safe.
Last updated June 14, 2026
Was this helpful?