astro-create-portal
v1.0.2
Published
Astro integration providing head-safe and keyed portal components for SSR and static builds.
Downloads
6
Maintainers
Readme
Features
- SSR & static ready: works during
astro dev, hybrid SSR, and fully static builds without custom scripts. - Head deduplication: merges
<meta>,<link>,<script>, and<style>tags by computed keys so only the latest unique entry survives. - Keyed portals: reuse
CreatePortalandPortalto bridge markup between distant parts of the DOM using a shared key. - No hydration overhead: all components render to
<template>tags and execute lightweight client-side runtime only when necessary.
Installation
Install with your preferred package manager. The package declares Astro as a peer dependency (>= 5).
npm install astro-create-portal
# or
pnpm add astro-create-portal
# or
bun add astro-create-portalOnce installed, register the integration in astro.config.mjs:
import astroPortal from 'astro-create-portal';
export default defineConfig({
integrations: [astroPortal()],
});Quickstart
- Add
<HeadPortal>inside your page/component to push markup into the document head. - Use
<CreatePortal key="..." />to define a target location. - Render
<Portal key="...">...</Portal>elsewhere to project content to that target.
The runtime script is injected automatically in development; during builds, markup is hoisted and deduplicated ahead of time.
Components
HeadPortal
Wrap any head-friendly markup in <HeadPortal> to duplicate the rendered output into the <head> element. Supports <title>, <meta>, <link>, <style>, <script>, and <base> tags. Duplicate entries are eliminated based on deterministic keys.
CreatePortal
<CreatePortal key="sidebar" /> declares a named insertion point. At build time it is replaced with a comment marker, and during hydration-free runtime the first matching marker receives the projected content.
Portal
<Portal key="sidebar">...children...</Portal> moves its slot content to the matching CreatePortal marker. The move happens after DOMContentLoaded and on every astro:page-load navigation in dev/SPA modes.
Each component validates the key prop (string/number/bigint) and throws an informative error if it is missing or empty.
Runtime behaviour
- Injected only in development: the runtime script scans for
<template>markers and performs DOM moves without hydration islands. - On first render and on SPA navigations (
astro:page-load), head portals are hoisted before ordinary portals resolve. document.headentries are deduplicated by tag type and identifying attributes (name,property,rel,href, etc.), ensuring predictable metadata.
Build integration
During astro build, the integration:
- Loads each generated HTML page with JSDOM.
- Hoists
<HeadPortal>markup into<head>and deduplicates entries. - Resolves keyed portals by cloning their content into the corresponding marker position.
- Writes the transformed HTML back to disk so no client runtime is needed for static hosting.
Example (Astro)
---
import { HeadPortal, CreatePortal, Portal } from 'astro-create-portal';
---
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Portal Demo</title>
</head>
<body>
<HeadPortal>
<title>Contact</title>
<meta name="description" content="Reach out to the team" />
<link rel="canonical" href="https://example.com/contact" />
</HeadPortal>
<CreatePortal key="footer-contact" />
<main>
<h1>Welcome</h1>
<Portal key="footer-contact">
<aside>
<h2>Contact</h2>
<p>Email us at [email protected]</p>
</aside>
</Portal>
</main>
</body>
</html>FAQ
Is this inspired by React's createPortal?
Yes! The API and keyed approach take cues from React's portal primitives, reworked to align with Astro's server-first rendering and static output pipeline.
Do portals support multiple targets with the same key?
Only the first CreatePortal with a given key is used. Define unique keys for each target.
Can I portal into the <head>?
Use <HeadPortal> for head content. Regular portals operate within the document body.
Does it work with view transitions or SPA mode?
Yes. The runtime reprocesses templates on astro:page-load, covering client-side navigations.
License
This project is licensed under the MIT License.
- Copyright (c) 2025 Jason Giese (Bl4cky99)
- See the full text in LICENSE.
Happy portaling!
