Framework Integration

Copy-pasteable patterns for the script integration across the frameworks publishers actually use.

These patterns are for the script integration. If your framework's hydration model fights the snippet (e.g. you keep losing the script-inserted DOM on navigation), switch to the API (browser) or API (server) path; you do the rendering yourself and there's nothing for hydration to tear down.

How the script tag behaves

When the page parses the <script>, our code reads its data-placement attribute and inserts a styled <div> at the script's position in the DOM. The div expands to fill its parent, so whatever wrapper you put the script in becomes the ad slot. One ad rendered per script load.

The patterns below all do the same thing in framework idiom: ensure the script runs after the wrapper is in the DOM, and re-run it on remount so the ad sticks around through navigation.

React (Client Component)

Inject the script in a useEffect and clean it up on unmount. This works in any React app: CRA, Vite, Remix client, Next.js client components.

"use client";
import { useEffect, useRef } from "react";

export function AdBanner({ placementId }: { placementId: string }) {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const script = document.createElement("script");
    script.async = true;
    script.src = "https://adventory.to/ad.banner.js";
    script.id = "adventory-" + placementId;
    script.dataset.placement = placementId;
    ref.current?.appendChild(script);
    return () => { script.remove(); };
  }, [placementId]);

  return <div ref={ref} />;
}

Next.js (App Router)

In the App Router, prefer a Client Component using the React pattern above. A <script> rendered in a Server Component does run on first page load, but the inserted ad div lives in DOM that React replaces on every soft navigation, so the ad disappears as soon as the visitor clicks an internal link. A Client Component re-runs the injection on mount, which keeps the ad consistent across route changes.

Recommended: Client Component

// app/components/AdBanner.tsx
"use client";
import { useEffect, useRef } from "react";

export function AdBanner({ placementId }: { placementId: string }) {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const script = document.createElement("script");
    script.async = true;
    script.src = "https://adventory.to/ad.banner.js";
    script.id = "adventory-" + placementId;
    script.dataset.placement = placementId;
    ref.current?.appendChild(script);
    return () => { script.remove(); };
  }, [placementId]);

  return <div ref={ref} />;
}

// Use it from any Server Component:
// <AdBanner placementId="..." />

Alternative: next/script

For pages that don't navigate (a static landing page), next/script with strategy="afterInteractive" works:

import Script from "next/script";

export default function Page() {
  return (
    <>
      <div id="ad-slot" />
      <Script
        src="https://adventory.to/ad.banner.js"
        data-placement="YOUR_PLACEMENT_ID"
        id="adventory-YOUR_PLACEMENT_ID"
        strategy="afterInteractive"
      />
    </>
  );
}

Astro

Drop the script directly into your .astro markup. Astro doesn't hydrate plain HTML by default, so the script tag works as a static include and the inserted ad div is left alone.

---
// src/components/AdBanner.astro
const { placementId } = Astro.props;
---

<div>
  <script
    is:inline
    async
    src="https://adventory.to/ad.banner.js"
    data-placement={placementId}
    id={`adventory-${placementId}`}
  ></script>
</div>

The is:inline directive tells Astro to render the tag as-is rather than processing it. Use the component anywhere: <AdBanner placementId="..." />.

SvelteKit

Use a +page.svelte with an onMount that injects the script. SvelteKit's client-side router replaces page content on navigation, so the mount/unmount lifecycle matches what you want.

<script lang="ts">
  import { onMount } from "svelte";

  export let placementId: string;
  let container: HTMLDivElement;

  onMount(() => {
    const script = document.createElement("script");
    script.async = true;
    script.src = "https://adventory.to/ad.banner.js";
    script.id = "adventory-" + placementId;
    script.dataset.placement = placementId;
    container.appendChild(script);
    return () => script.remove();
  });
</script>

<div bind:this={container}></div>

Vue 3

Mount the script in onMounted and clean up in onUnmounted:

<script setup lang="ts">
import { onMounted, onUnmounted, ref } from "vue";

const props = defineProps<{ placementId: string }>();
const container = ref<HTMLDivElement | null>(null);
let script: HTMLScriptElement | null = null;

onMounted(() => {
  script = document.createElement("script");
  script.async = true;
  script.src = "https://adventory.to/ad.banner.js";
  script.id = "adventory-" + props.placementId;
  script.dataset.placement = props.placementId;
  container.value?.appendChild(script);
});

onUnmounted(() => {
  script?.remove();
});
</script>

<template>
  <div ref="container"></div>
</template>

When the script fights your framework

Some setups (heavy SPA routers, frameworks that aggressively diff the DOM, strict CSP that blocks third-party scripts) make the snippet brittle. The escape hatch is the JSON API: fetch the ad as data, render it with your own components, fire the impression pixel yourself.