withastro/astro · error · Error

You tried to add the

Error message

You tried to add the ${name} client renderer, but its server renderer wasn't added. You must add the server renderer first. Use the `addServerRenderer` function.

What it means

`addClientRenderer` registers the client entrypoint used for `client:*` hydration by looking up an already-registered server renderer with the same name in the container's manifest. No renderer with that name exists yet, so the client renderer would be unreachable — the call is rejected and the message points at `addServerRenderer`.

Solutions

  1. Register the server renderer first with the same name, then the client renderer
  2. Make both names identical — by convention the package name, e.g. `@astrojs/react`
  3. Load the client entrypoint from the package's `client.js` export (e.g. `import entrypoint from '@astrojs/react/client.js'`)

Example fix

// before
container.addClientRenderer({ name: '@astrojs/react', entrypoint });
container.addServerRenderer({ name: '@astrojs/react', renderer });

// after
container.addServerRenderer({ name: '@astrojs/react', renderer });
container.addClientRenderer({ name: '@astrojs/react', entrypoint });
Defensive patterns

Strategy: validation

Validate before calling

// track registrations yourself and enforce ordering in a helper
const registeredServerRenderers = new Set();

function addRendererPair(container, name, serverRenderer, clientEntrypoint) {
  container.addServerRenderer({ name, renderer: serverRenderer });
  registeredServerRenderers.add(name);
  if (clientEntrypoint) {
    if (!registeredServerRenderers.has(name)) {
      throw new Error(`server renderer '${name}' must be registered first`);
    }
    container.addClientRenderer({ name, entrypoint: clientEntrypoint });
  }
}

Prevention

When it happens

Trigger: Calling `addClientRenderer({ name, entrypoint })` before `addServerRenderer({ name, renderer })`; using a name that does not exactly match the server renderer's name (missing `@scope/`, typo, different casing).

Common situations: Bootstrapping container tests that render hydrated components; copy-pasted setup where the two names drift apart ('react' vs '@astrojs/react'); ordering mistakes in test helpers.

Related errors


AI-assisted analysis of withastro/astro@3578d45d34 (2026-08-18). Data as JSON: /api/errors/1158268204a44256. Report an issue: GitHub.

Appendix: source

Thrown at packages/astro/src/container/index.ts:452

	 *
	 * const container = await AstroContainer.create();
	 * container.addServerRenderer(reactRenderer);
	 * container.addClientRenderer({
	 * 	name: "@astrojs/react",
	 * 	entrypoint: "@astrojs/react/client.js"
	 * });
	 * ```
	 *
	 * @param options {object}
	 * @param options.name The name of the renderer. The name **isn't** arbitrary, and it should match the name of the package.
	 * @param options.entrypoint The entrypoint of the client renderer.
	 */
	public addClientRenderer(options: AddClientRenderer): void {
		const { entrypoint, name } = options;

		const rendererIndex = this.#manifest.renderers.findIndex((r) => r.name === name);
		if (rendererIndex === -1) {
			throw new Error(
				'You tried to add the ' +
					name +
					" client renderer, but its server renderer wasn't added. You must add the server renderer first. Use the `addServerRenderer` function.",
			);
		}
		const renderer = this.#manifest.renderers[rendererIndex];
		renderer.clientEntrypoint = entrypoint;

		this.#manifest.renderers[rendererIndex] = renderer;
	}

	// NOTE: we keep this private via TS instead via `#` so it's still available on the surface, so we can play with it.
	// @ts-expect-error @ematipico: I plan to use it for a possible integration that could help people
	private static async createFromManifest(
		manifest: SSRManifest,
	): Promise<experimental_AstroContainer> {
		const container = new experimental_AstroContainer({
			manifest,

View on GitHub (pinned to 3578d45d34)