Add Cookie Compliance to an Astro site

Cookbook · platform Last verified View raw markdown (for agents)

In short: paste the live snippet into the shared layout's <head>, and add is:inline to every Cookie Compliance <script> tag. Without it, Astro silently rewrites the tags into deferred Vite modules — the banner may still render, but blocking no longer holds.

You need #

The wrong way #

---
// src/layouts/Layout.astro — BAD
---
<html lang="en">
  <head>
    <script>
      var huOptions = { "appID": "YOUR_APP_ID", "blocking": true };
    </script>
    <script src="https://cdn.hu-manity.co/hu-banner.min.js"></script>
    <slot name="head" />
  </head>
  <body><slot /></body>
</html>

This looks identical to the plain-HTML version, and that's the trap. Astro processes every <script> tag by default — even one with a real src URL — bundling and hoisting it through its own module graph, unless the tag has is:inline or any attribute other than src. The result renders as <script type="module" src="/src/layouts/Layout.astro?astro&type=script&index=0&lang.ts">: a deferred module, not the synchronous inline script huOptions has to be for hu-banner.min.js to read it before running.

Verified 2026-09-29: pasting the exact two-tag live snippet with no is:inline produced exactly this — the huOptions script became a deferred Vite module, and a bare third <script src="..."> tracker tag (no type attribute, matching a typical GA4/gtag snippet) was also rewritten into a second deferred module. Only the Cookie Compliance CDN tag survived unprocessed, because it happened to carry an explicit type="text/javascript" attribute — which is incidental, not something to rely on.

The right way #

Add is:inline to every script tag you want Astro to leave completely alone, in the shared layout:

---
// src/layouts/Layout.astro
---
<html lang="en">
  <head>
    <!-- Cookie Compliance: FIRST scripts on the page (paste live snippet here) -->
    <script is:inline>
      var huOptions = { "appID": "YOUR_APP_ID", "currentLanguage": "en", "blocking": true, "globalCookie": false };
    </script>
    <script is:inline src="https://cdn.hu-manity.co/v2/hu-banner.min.js" type="text/javascript" charset="utf-8"></script>
    <!-- A new app. If the live snippet has no /v2/, the app is on v1 — keep that URL. -->

    <slot name="head" />
  </head>
  <body><slot /></body>
</html>

Existing trackers, wherever they're declared (this layout, a page, a component), need is:inline too if you want their tag to render exactly as written — otherwise Astro may still bundle them, which doesn't defeat blocking on its own but does mean you're no longer looking at the tag you wrote when you check placement.

Verified 2026-09-29: with is:inline on all three tags, the rendered response had all three as literal <script> elements in source order, none converted to a module.

Check it works #

Run verify-install (skills/verify-install/SKILL.md) against the running site. Astro-specific extra:

# Check Pass
A1 View source (not devtools' rendered DOM) on / and an inner route huOptions and the hu-banner.min.js (or /v2/…) tag are literal <script> elements — not <script type="module" src="...?astro&type=script..."> — as the first two scripts, in order.
A2 Grep the layout for every Cookie Compliance <script> tag Each one has is:inline.

Verified 2026-09-29 against a live local Astro dev server, a real (non-production) test AppID, and a real googletagmanager.com/gtag/js tag as the tracker, in a fresh isolated browser context: with is:inline in place, the tracker rendered type="javascript/blocked" and no tracking cookie appeared before consent — matching the shared verify-install checklist's check 3.

Gotchas #