Client-Side Scripts
Purpose and Scope
Astro gives authors two complementary ways to send JavaScript to the browser. The first is the ordinary HTML script experience inside Astro components, where a script can attach event listeners, import npm packages, run analytics, or update DOM state without adopting a UI framework. The official guide describes Astro as enhancing plain script tags with TypeScript, bundling, module output, deduplication, and automatic inlining when appropriate. The second path is island hydration, where framework integrations register browser entrypoints that hydrate or render React, Preact, Solid, and other component trees only where interactivity is needed.
This page explains the runtime pieces that make those browser capabilities fit together. A “client directive” is the small browser-side function behind directives such as framework hydration modes. A “client action” is the browser proxy for Astro Actions, responsible for serializing input, making the internal request, and deserializing the result. A “framework client entrypoint” is the integration-owned adapter function that receives the island element, component, props, slots, and hydration metadata, then calls the framework’s own render or hydrate API. Sources: packages/astro/src/core/client-directive/build.ts, packages/astro/src/actions/runtime/entrypoints/client.ts, packages/integrations/react/src/client.ts
Relevant Source Files
- packages/astro/src/core/client-directive/build.ts — builds a client directive entrypoint with esbuild so the generated code can run directly inside a browser script tag and register itself on
self.Astro. - packages/astro/src/actions/runtime/client.ts — defines shared client-safe Action runtime types and errors, including
ActionError,ActionInputError, status-code mapping, and JSON deserialization helpers used by the browser entrypoint. - packages/astro/src/actions/runtime/entrypoints/client.ts — exports the public client-side
astro:actionssurface, blocks server-only APIs on the client, builds action paths, serializes request bodies, fetches action endpoints, and deserializes responses. - packages/integrations/preact/src/client.ts — implements the Preact island client, including SSR checks, slotted HTML handling, signal sharing,
client:onlyrendering, hydration, and unmount cleanup. - packages/integrations/react/src/client.ts — implements the React island client using
createRoot,hydrateRoot,startTransition, slot conversion, form action state, root reuse, and unmount cleanup. - packages/integrations/solid/src/client.ts — implements the Solid island client with slot discovery, store reconciliation for rerenders,
hydrateversusrender,client:onlyfallback clearing, and disposal on unmount.
System-to-Code Mapping
The lowest-level client directive build step is intentionally small. buildClientDirectiveEntrypoint(name, entrypoint, root) creates an esbuild bundle from virtual stdin, imports the directive module, stores it as self.Astro[name], and dispatches an astro: event for that directive name. The output format is an immediately invoked function expression, minified and bundled, with write: false so the caller receives text that can be inserted into a script response or generated asset. This is the bridge between server-side knowledge of a directive entrypoint and browser-side availability of the directive function. Sources: packages/astro/src/core/client-directive/build.ts
Astro Actions add another browser-facing layer. The client entrypoint imports virtual configuration for trailing-slash behavior and adapter-specific internal fetch headers, then exports actions, getActionPath, ActionError, isActionError, isInputError, ACTION_QUERY_PARAMS, and related types. It deliberately defines defineAction() and getActionContext() as throwing functions on the client because those are server authoring APIs, not browser APIs. That distinction helps keep the same module namespace ergonomic while preventing accidental server-only behavior from running in the browser. Sources: packages/astro/src/actions/runtime/entrypoints/client.ts, packages/astro/src/actions/runtime/client.ts
Framework islands are integration-specific because each renderer has its own hydration contract. The React client creates React elements from components and slots, can convert DOM children when the experimental children marker is present, preserves form action state from island attributes, and uses a WeakMap to reuse roots across renders. The Preact client hydrates with Preact’s hydrate, renders client:only islands with render, and seeds Preact’s internal id mask to avoid repeated island roots colliding. The Solid client uses a store per island so subsequent calls reconcile props and slots rather than remounting unnecessarily. Sources: packages/integrations/react/src/client.ts, packages/integrations/preact/src/client.ts, packages/integrations/solid/src/client.ts
Execution Flow
For plain component scripts, the authoring model starts in an .astro file: place a script tag in the component template, import packages or local modules, and write browser code against the DOM. The official docs emphasize that this is enough for common interaction such as click handlers, animations, analytics, or dynamic text updates. When a UI framework is not required, this path avoids sending framework runtime code. The repository evidence here does not include the compiler script transform itself, but the client directive builder shows the same design goal: browser code is bundled, minimized, and made directly executable.
For an island, the server-rendered page contains an island element with attributes describing whether it was SSR-rendered, which client mode is active, serialized props, slots, and renderer-specific metadata. Once Astro’s browser runtime loads the relevant directive and renderer client, the integration entrypoint receives the host element and returns a function that mounts the component. All three integration clients first check the ssr attribute and return early when the element is not in the expected state. After that, they convert slotted HTML into renderer-specific children, choose hydrate or render based on client, and subscribe to astro:unmount for cleanup. Sources: packages/integrations/preact/src/client.ts, packages/integrations/react/src/client.ts, packages/integrations/solid/src/client.ts
The Actions client flow is request oriented rather than component oriented. Calling a generated action through actions passes input to handleAction, which prepares Accept: application/json, applies adapter-provided internal headers, and keeps FormData bodies as form data. Non-form input is serialized with JSON.stringify; serialization failure becomes an ActionError with BAD_REQUEST. The request is posted to the computed action URL. A 204 response is deserialized as empty, an OK response is treated as application/json+devalue data, and a non-OK response is deserialized as a JSON error. Sources: packages/astro/src/actions/runtime/entrypoints/client.ts
API Components
| Component | Public or internal shape | Browser behavior |
|---|---|---|
buildClientDirectiveEntrypoint(name, entrypoint, root) | Internal build helper | Bundles a directive module, assigns it to self.Astro[name], and dispatches astro:name. |
ActionError | Exported client-safe error class | Maps Astro action error codes to HTTP status values and reconstructs action errors from JSON. |
ActionInputError | Exported subclass of ActionError | Carries serializable validation issues and field-oriented input errors without importing the full Zod error object into the browser. |
getActionPath | Exported client helper | Uses BASE_URL plus trailing-slash options to compute action URLs. |
actions | Exported client proxy | Serializes input, posts to the action endpoint, and deserializes empty, data, or error results. |
| React integration default export | Renderer client factory | Uses hydrateRoot for SSR islands, createRoot for client:only, and startTransition around updates. |
| Preact integration default export | Renderer client factory | Uses hydrate for SSR islands, render for client:only, and shared signal mapping for serialized Preact signals. |
| Solid integration default export | Renderer client factory | Uses hydrate for SSR islands, render for client:only, and store reconciliation for repeated updates. |
The practical API boundary for application developers remains simple: write a script when DOM-level JavaScript is enough, choose a framework island when component state and framework ecosystems are useful, and use Actions when browser code needs to call typed server logic. The source files show that Astro keeps these capabilities separate internally. Directive code is built as executable browser registration, action code is a request/response proxy, and renderer clients are small adapters over each framework’s own hydration primitives. Sources: packages/astro/src/core/client-directive/build.ts, packages/astro/src/actions/runtime/entrypoints/client.ts, packages/integrations/react/src/client.ts
Implementation Details and Edge Cases
Several implementation details matter when debugging client behavior. React tracks already hydrated containers by scanning element properties that begin with React’s internal container prefix, then deletes that marker for nested components to suppress aggressive warnings before reusing a root from a WeakMap. Preact handles both Astro slots and Preact signals, replacing serialized signal references with shared signal objects so multiple props can reference the same browser signal. Solid records an update function in a WeakMap and calls reconcile on later renders, which preserves reactivity while applying minimal changes. Sources: packages/integrations/react/src/client.ts, packages/integrations/preact/src/client.ts, packages/integrations/solid/src/client.ts
Unmount behavior is also standardized at the Astro boundary while remaining renderer-specific inside each integration. The React client registers r.unmount(), Preact renders null into the element, and Solid calls the disposer returned by hydrate or render. All three listen for the astro:unmount event once. This makes page-level lifecycle features, including client navigation and island replacement, able to ask renderer code to clean itself up without the core runtime needing to know each framework’s disposal API. Sources: packages/integrations/preact/src/client.ts, packages/integrations/react/src/client.ts, packages/integrations/solid/src/client.ts
Next Steps
Use plain Astro component scripts first when the interaction can be expressed with standard browser APIs and a small amount of JavaScript. Move to framework islands when you need React, Preact, Solid, or another renderer for component state, hooks, signals, or ecosystem components. Use the Actions client entrypoint when client-side interaction needs to call server logic and receive typed data or structured errors. For adjacent topics, continue with framework components, Actions, directives and syntax, and the broader API reference.