Skip to main content

Astro Components

ConsentBannerDeferred

Render the banner in a server island

Use ConsentBannerDeferred in place of ConsentBanner when the page HTML is cached or prerendered but the banner must still come from each visitor's own request:

src/layouts/cached.astro
---
import { ClientRouter } from 'astro:transitions';
import {
	ConsentDialog,
	ConsentDialogLink,
	ConsentScript,
} from 'c15t/astro/components';
import ConsentBannerDeferred from 'c15t/astro/components/consent-banner-deferred.astro';

interface Props {
	title: string;
}

const { title } = Astro.props;
---

<html lang="en">
	<head>
		<meta charset="utf-8" />
		<meta content="width=device-width, initial-scale=1" name="viewport" />
		<title>{title}</title>
		<ConsentScript />
		<ClientRouter />
	</head>
	<body>
		<slot />
		<footer>
			<ConsentDialogLink>Privacy settings</ConsentDialogLink>
		</footer>
		<ConsentBannerDeferred />
		<ConsentDialog />
	</body>
</html>

ConsentBannerDeferred renders ConsentBanner with Astro's server:defer directive. The page HTML stays the same for everyone. After the page loads, Astro requests the island from /_server-islands/ConsentBanner. That request carries the visitor's cookie and location headers, so the c15t middleware resolves consent for it and the banner arrives with the visitor's own policy.

When to use it

PageUse
Rendered on each requestConsentBanner
Served from a CDN cache, or prerendered, on a site with a server adapterConsentBannerDeferred
Built by a static site with no adapterConsentBanner, which the browser renders after /init

Server islands need a server adapter. A purely static build cannot answer the island request. See cache a page and render the banner per request.

Props

ConsentBannerDeferred passes its props to ConsentBanner:

PropTypeDefaultEffect
titlestringTranslationBanner heading
descriptionstringTranslationBanner body text
acceptButtonTextstringcommon.acceptAllAccept button label
rejectButtonTextstringcommon.rejectAllReject button label
customizeButtonTextstringcommon.customizeCustomize button label
dismissButtonTextstringcommon.acknowledgeLabel of the button that acknowledges a notice
legalLinks('privacyPolicy' | 'cookiePolicy' | 'termsOfService')[] | nullNoneWhich configured legal links to show
hideBrandingbooleanfalseRemoves the "Secured by" tag
noStylebooleanfalseRenders the markup without c15t's class names
classstringNoneExtra class on the banner root
forcebooleanfalseRenders the banner even when the server decided to hide it

Astro serializes the props of a server island for the island request, so pass plain strings, booleans and arrays. See ConsentBanner for what each prop does.

ConsentBannerDeferred also passes the page's Astro.locals.c15t.nonce to the island, so the inline code the island inserts carries the nonce the page's policy allows. Astro's own island loader carries no nonce, so a nonce policy from your middleware blocks the island. See Content Security Policy.

Show something while the island loads

Pass a fallback slot. Astro shows it until the island's response replaces it:

src/layouts/cached.astro (partial)
<ConsentBannerDeferred>
  <div slot="fallback" aria-hidden="true"></div>
</ConsentBannerDeferred>

Most sites leave the fallback empty. A visitor who has already chosen gets an empty island response, and a placeholder the size of a banner would flash and disappear for them.

What stays the same

  • ConsentScript still goes in <head>. On a prerendered page it inlines a payload with no visitor state, and the island brings the visitor's own decision.
  • ConsentDialog and ConsentDialogLink stay in the cached layout. They do not depend on the visitor.
  • Accessibility, attributes and styling match ConsentBanner.

Check the server island

View the page source of a first visit. It contains no banner markup. In DevTools Network, a request to /_server-islands/ConsentBanner returns it. Accept or reject, then reload: the island request returns no banner. Vendor requests stay absent until you allow their category. See Verify consent.

Next steps