web-meta-framework-astro-df3ec3 — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited web-meta-framework-astro-df3ec3 (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Quick Guide: Astro renders pages to static HTML with zero client JavaScript by default..astrocomponents carry static content; framework components become interactive islands only when given aclient:*directive. Content collections give structured Markdown type-safe frontmatter through Zod schemas. Rendering mode is per page: static unlessexport const prerender = false. Astro 6 replaced<ViewTransitions />with<ClientRouter />, replacedAstro.glob()withimport.meta.glob(), movedzfromastro:contenttoastro/zod(now Zod 4), requires stringgetStaticPaths()params, and requires Node 22.12.0+.
Detailed Resources:
client:only, multi-framework islands, server islands, cross-island stateoutput setting and no adapter; dynamic routes needgetStaticPaths(). Follow examples/routing.md.
export const prerender = false tothose pages, and install a server adapter. Same file, plus the adapter table in reference.md.
output: "server" and opt individual pages back tostatic with export const prerender = true.
<critical_requirements>
Leave pages static and add `export const prerender = false` only where the page reads request-time data. Static is the default and is where the framework's speed comes from.
Install a server adapter before any page opts out of prerendering. Without one, a build containing prerender = false fails.
*Give a framework component a `client:` directive when it needs to be interactive.** Without a directive it renders to static HTML and its event handlers never run.
Define collections in `src/content.config.ts` with a Zod schema. The schema validates frontmatter at build time and generates the types the queries return.
Use `getStaticPaths()` for dynamic routes in static mode. On-demand routes read Astro.params directly and need no such export.
</critical_requirements>
Auto-detection: Astro, .astro files, astro.config, islands architecture, client:load, client:visible, client:idle, client:only, client:media, server:defer, content collections, defineCollection, defineLiveCollection, getCollection, getLiveCollection, getEntry, getLiveEntry, render, astro:content, astro:transitions, ClientRouter, getStaticPaths, Astro.props, Astro.params, Astro.cookies, Astro.redirect, prerender, astro add, @astrojs/react, @astrojs/vue, @astrojs/svelte, Starlight
Applies to:
.astro component syntax — frontmatter, template expressions, slots, layouts, scoped stylesprerender control<ClientRouter />, transition directives, persisted elementsHandled elsewhere:
library wrote it, and Astro settles only the boundary.
<style> block — the scoping rules here are Astro's; the styling system is not.whose default is on-demand rendering rather than one whose default is static output.
<philosophy>
Astro is a content-first framework that ships zero JavaScript by default. Most of a page is static HTML; small interactive "islands" hydrate independently, each paying only for itself.
Four consequences follow, and they explain most of the API:
client:* is the opt-in, so the cost ofinteractivity is always visible at the call site.
contents, and one page can carry several.
that reads it, so a typo in a post fails the build rather than the page.
prerender is an export rather than a global setting.
</philosophy>
<decision_framework>
Static or on-demand? A page needs on-demand rendering when it reads cookies, headers or user-specific data at request time, or when its data changes faster than you are willing to rebuild. Everything else is static — including data that changes hourly, which a rebuild handles more cheaply than an adapter does.
`.astro` or a framework component? No client-side interactivity means .astro — zero JavaScript. Simple interactivity (a toggle, a show/hide) is .astro plus a <script> tag, which is lighter than a framework. Reach for a framework component when the interaction needs that framework's own state model.
Which client directive? The table in Pattern 4 maps each one to what it is for. client:only is the last resort, for a component that cannot server-render at all.
Build-time or live collection? defineCollection for anything that is the same on every request — posts, docs, changelogs, author bios. defineLiveCollection only when the data must be fresh per request (inventory, pricing), which also forces that page into on-demand rendering.
</decision_framework>
<patterns>
An .astro file is a server-only script between --- fences followed by an HTML template. The script never reaches the browser, so data fetching belongs there.
---
interface Props { title: string; description?: string }
const { title, description = "Default" } = Astro.props;
const posts = await getCollection("blog");
---
<h1>{title}</h1>
{posts.map((post) => <a href={`/blog/${post.id}`}>{post.data.title}</a>)}
<style>h1 { color: navy; }</style>Styles in a <style> block are scoped to the component unless marked is:global.
Full code: examples/core.md
A <slot /> receives content from the parent. Named slots take a matching slot="name" attribute, and anything inside the <slot> element is fallback content when nothing is passed.
<article>
<header><slot name="header"><h2>{title}</h2></slot></header>
<slot />
<footer><slot name="footer" /></footer>
</article>Full code: examples/core.md
A layout is an ordinary component that wraps page content through its default slot, and layouts nest — a blog layout wrapping a base layout wrapping the page.
---
const { title } = Astro.props;
---
<html lang="en">
<head><title>{title}</title></head>
<body><main><slot /></main></body>
</html>Full code: examples/core.md
Framework components render to static HTML until a client:* directive hydrates them. The directive chooses _when_ hydration happens.
| Directive | Hydrates | Use for |
|---|---|---|
client:load | Immediately on page load | Critical interactive UI |
client:idle | When the browser is idle | Lower-priority interactivity |
client:visible | When it scrolls into the viewport | Below-the-fold content |
client:media | When a media query matches | Responsive interactivity |
client:only | Client only, skipping the server render | Browser-dependent components |
<Header /> <!-- static, zero JS -->
<SearchBar client:load />
<Comments client:visible />
<Analytics client:media="(max-width: 768px)" />Full code: examples/islands.md
server:defer renders a component per request while the rest of the page stays cacheable. The fallback slot holds its place until it arrives.
<ProductInfo product={product} />
<UserReviews server:defer>
<div slot="fallback">Loading reviews...</div>
</UserReviews>Full code: examples/islands.md
A collection pairs a loader with a Zod schema. getCollection queries it with an optional filter, and render() turns an entry's Markdown into a component.
// src/content.config.ts
const blog = defineCollection({
loader: glob({ pattern: "**/*.md", base: "./src/content/blog" }),
schema: z.object({
title: z.string(),
pubDate: z.coerce.date(),
draft: z.boolean().default(false),
}),
});
export const collections = { blog };const posts = await getCollection("blog", ({ data }) => !data.draft);
const { Content, headings } = await render(post);Live collections are a separate mechanism: defineLiveCollection in src/live.config.ts (not content.config.ts), queried with getLiveCollection() / getLiveEntry(), and only from on-demand pages.
Full code: examples/content.md
src/pages/ maps to URLs. [param] is a single dynamic segment, [...rest] matches any depth, and a leading underscore excludes a file from routing.
src/pages/
├── index.astro → /
├── blog/[id].astro → /blog/:id
├── docs/[...slug].astro → /docs and /docs/*
└── _helpers.ts → not a routeIn static mode each dynamic route exports getStaticPaths() returning { params, props } pairs; paginate() inside it generates numbered pages.
Full code: examples/routing.md
export const prerender = false moves a page to request time, where Astro.cookies, Astro.redirect() and a Response return value all become available. API endpoints are .ts files exporting one handler per HTTP method, and they default to static like everything else.
export const prerender = false;
export const GET: APIRoute = async ({ url }) => {
const query = url.searchParams.get("q");
if (!query)
return new Response(JSON.stringify({ error: "Missing query" }), {
status: 400,
});
return new Response(JSON.stringify(await searchDatabase(query)));
};Full code: examples/routing.md
<ClientRouter /> in the document head enables cross-page transitions. transition:name pairs elements so they morph, and transition:persist keeps an element alive across a navigation.
<head><ClientRouter /></head>
<img src={post.data.heroImage} transition:name={`hero-${post.id}`} transition:animate="slide" />
<audio controls transition:persist><source src="/music.mp3" type="audio/mp3" /></audio>Full code: examples/integrations.md
</patterns>
<red_flags>
Breaks at runtime:
getStaticPaths() — the build fails with "getStaticPaths() is required".prerender = false with no server adapter installed — the build fails.<ViewTransitions /> — removed in Astro 6; import <ClientRouter /> from astro:transitions.Astro.glob() — removed in Astro 6; use import.meta.glob().import { z } from "astro:content" — removed in Astro 6; import z from astro/zod, which is Zod 4, so z.string().email() is now z.email().getStaticPaths() params — Astro 6 requires strings; wrap with String(id).astro.config.cjs — Astro 6 requires ESM (.mjs or .ts).Astro.props read outside the frontmatter fence, or an .astro component imported into a framework component — neither is available there.Surprising behaviour:
client:load on everything defeats the architecture; a component with no interactivity wants no directive at all.client:only skips the server render, so nothing is in the HTML for crawlers — prefer client:load wherever the component can render on the server.<script> tag creates a client waterfall; frontmatter runs server-side and has no such cost.output: "server" on a mostly-static site gives up the default's caching for pages that did not need it.getCollection returns drafts alongside published entries.<style> is scoped by default — reach for <style is:global> or the :global() selector deliberately.<script> is bundled and deduped; is:inline opts out, which is what makes a script re-run on every View Transitions navigation.client:visible waits on IntersectionObserver, so a component that is never on screen never hydrates.Astro.redirect() works only on on-demand pages; a static page cannot redirect at request time.transition:persist needs the same transition:name on both pages, or the element is recreated.import.meta.env is inlined at build time — read runtime secrets from process.env.[...slug] route matches its own base path when a params entry passes undefined.</red_flags>
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.