Skip to content

Vite Virtual Module


The Eminence Astro Suite uses a Vite virtual module (virtual:eminence-astro-suite/head-tags) as an internal mechanism to distribute default head tag configuration to components at build time. This module is automatically created and injected by the integration—users do not create, configure, or import it directly.

The virtual module serves a single purpose: it makes configuration defaults available to head tag components without requiring those defaults to be passed as props on every page.


The virtual module is registered with the ID:

"virtual:eminence-astro-suite/head-tags";

This ID is used internally by the integration to:

  1. Create a Vite virtual module resolver
  2. Generate and inject configuration at build time
  3. Make the module available to components during build and runtime

The integration works with two distinct types that reflect the same head tag settings at different stages:

TagInput — the user-facing type. This is the shape of the head option in astro.config.mjs. Every field is optional. Users only need to specify the values they want to change.

// What the user configures
type TagInput = {
extend?: {
link?: Array<HTMLAttributes<"link"> & { prefetch?: boolean }>;
meta?: Array<HTMLAttributes<"meta"> & { property: string }>;
custom?: string | string[];
};
appleItunesApp?: ComponentProps<typeof AppleItunesApp>;
base?: ComponentProps<typeof Base>;
charset?: string;
colorScheme?: string;
generator?: boolean;
humansTxt?: string | URL | boolean;
icons?: IconsOptions | false;
manifest?: string | boolean;
openGraphSiteName?: string;
robots?: ComponentProps<typeof Robots>;
themeColor?: string | { light: string; dark: string };
titleTemplate?: string;
verification?: ComponentProps<typeof Verification>;
viewport?: string;
};

ResolvedTagConfig — the virtual module output type. This is what the virtual module actually exports after defaults have been applied. Fields that always have a default value (charset, viewport, titleTemplate, generator, icons, manifest, humansTxt) are required and non-nullable in this type. This means components reading from the virtual module never need fallback guards for those fields.

// What components receive from the virtual module
// Defaulted fields are required; optional fields remain optional
type ResolvedTagConfig = {
charset: string; // default: "utf-8"
viewport: string; // default: "width=device-width, initial-scale=1"
titleTemplate: string; // default: "%s"
generator: boolean; // default: true
icons: IconsOptions | false; // default: {}
manifest: string | boolean; // default: false
humansTxt: string | URL | boolean; // default: "/humans.txt"
extend?: {
link?: Array<HTMLAttributes<"link"> & { prefetch?: boolean }>;
meta?: Array<HTMLAttributes<"meta"> & { property: string }>;
custom?: string | string[];
};
// ...all other TagInput fields remain optional
};

Server-only options (like robotsTxt, securityTxt, sitemap) are never included in the virtual module and cannot be accessed by components.

When you configure the integration:

astro.config.mjs
eminence({
head: {
charset: "UTF-8",
viewport: "width=device-width, initial-scale=1",
titleTemplate: "%s | My Site",
},
});

The integration:

  1. Extracts only the head field
  2. Filters it to client-safe fields
  3. Serializes it as a JSON object
  4. Creates a JavaScript module that exports this object as the default export
  5. Registers this module as a virtual file in Vite

The resulting virtual module looks like:

export default {
charset: "UTF-8",
viewport: "width=device-width, initial-scale=1",
titleTemplate: "%s | My Site",
};

The virtual module solves a fundamental problem in static site generation:

Problem: Head tag components need default values (like viewport, charset, title template) available on every page, but those values are only specified once in astro.config.mjs.

Solution: The virtual module makes configuration available at build time without requiring:

  • Manual prop passing on every page
  • Runtime server calls to fetch configuration
  • Duplicating defaults across your site

For custom tags, prefer inserting one-off elements directly in the Head slot. Use extend.link and extend.meta when you need global defaults from integration config.

extend.custom is passed through to Astro set:html, so it is not escaped automatically. Only pass trusted or sanitized HTML strings.

If you are authoring components that should respect integration defaults, you can optionally import the virtual module:

// Inside a component or component helper
import config from "virtual:eminence-astro-suite/head-tags";
// Use defaults from config, but allow per-page overrides
const charset = pageCharset ?? config.charset;

The integration uses Vite’s resolveId and load hooks to:

  • Intercept imports of virtual:eminence-astro-suite/head-tags
  • Return the serialized configuration object

The virtual module is fully resolved at build time. There is no runtime overhead or dynamic module loading.

TypeScript users get full type support through the ResolvedTagConfig type, which is exported from the package:

import type { ResolvedTagConfig } from "eminence-astro-suite";

For configuring the integration in astro.config.mjs, use TagInput:

import type { TagInput } from "eminence-astro-suite";