How to read Astro you did not write
Every earlier module in this course taught you to write Astro. This module teaches the other half: reading Astro someone — or something — else wrote, and deciding whether it is correct. An AI assistant hands you a .astro file that compiles, that astro check accepts, and that looks exactly like the examples you have been studying. None of that proves it is correct — most of what makes AI-generated Astro wrong is invisible to the type checker on purpose. Astro’s own training-data problem makes this worse than most frameworks: the bulk of public Astro code an LLM has seen is shaped like Astro 2, 3, or 4 — output: 'hybrid', <ViewTransitions />, Astro.glob(), collections with no loader — all of it fluent-looking, all of it wrong on the Astro 7.3.5 this course documents. The seven steps below are the order that actually surfaces these failures, and every later lesson in this module drills one part of it.
flowchart TD A["1. Rendering mode & adapter<br/>static vs on-demand, prerender, is an adapter even installed"] --> B["2. Islands & client directives<br/>client:load reflex, serializable props, client:only"] B --> C["3. Content & data<br/>loader required, render(entry), Astro.glob() is gone"] C --> D["4. Server-side Astro<br/>every action is a public endpoint"] D --> E["5. Security<br/>set:html, secrets crossing the island boundary"] E --> F["6. Performance & caching<br/>routeRules vs Cache-Control, is:inline, transition:persist"] F --> G["7. Tooling & production<br/>deprecated config keys, astro preview is not a server"]
1. Rendering mode & adapter
Section titled “1. Rendering mode & adapter”Before anything else, work out when a piece of Astro actually runs — at build time, once, or per request, on a server — because every later question (can this read a cookie, does this need an adapter, will this header do anything) depends on the answer. AI-generated code frequently assumes a page renders per-request when it’s actually prerendered (or the reverse), and training data older than Astro v5 still reaches for a three-mode system Astro no longer has.
- What to look for: does the project (or the specific route) actually set
export const prerender = falseanywhere, or is it relying on a project-wideoutput: 'server'? Does the config still setoutput: 'hybrid'— a value Astro v5 removed entirely? Is there an adapter configured at all, if any route (or Action, or server island) needs on-demand rendering? Does a page readAstro.cookies,Astro.session, orAstro.request.headerswhile everything around it still says static? - The question to ask: does this file’s HTML genuinely depend on who’s asking, or is it the same for every visitor — and does the code’s own rendering-mode declaration actually match that answer?
- Which lesson explains it: Rendering & Output Modes for static vs on-demand and the real, proven build warning a prerendered page gets for reading request data; SSR & Adapters for adapters,
output, and why'hybrid'is a legacy term now.
2. Islands & client directives
Section titled “2. Islands & client directives”Once you know what renders when, check what actually ships to the browser. A framework component imported into .astro is static HTML until a client directive turns it into an island — and the directive an LLM reaches for by default is almost never the right one.
- What to look for: is every interactive component
client:load, regardless of where it sits on the page? Doesclient:onlyever appear without naming a framework? Is a prop passed into an island a plain value (serializable), or a function/class instance the wire format can’t carry? Does a framework component (.tsx/.jsx) use Astro’s own<slot />syntax, expecting it to work the way it does inside a.astrofile? - The question to ask: does this component need to be interactive the instant the page loads, or would a later trigger (
client:visible,client:idle) serve it just as well with less upfront JavaScript — and will every prop this island receives actually survive being serialized into the HTML? - Which lesson explains it: Client Directives for all five directives, the real
<astro-island>markup, and the provenNoClientOnlyHinterror; Framework Integrations for what changes with more than one UI framework installed; Sharing State for state that has to cross the island boundary correctly.
3. Content & data
Section titled “3. Content & data”Content collections are where pre-v5 training data is most obviously stale — the whole API (a required loader, render(entry) as a standalone function, Astro.glob() gone) changed shape, and old-looking code fails in specific, nameable ways rather than just “not compiling.”
- What to look for: does every
defineCollectioncall declare aloader? Does the code callentry.render()as a method, instead of importingrenderfromastro:content? Does a dynamic route that’s meant to be static actually exportgetStaticPaths()? Does frontmatter get validated by a schema that actually matches its real fields? Does anything still callAstro.glob()? - The question to ask: is this the current Content Layer API (a
loader, a Zodschema,render(entry)), or does it read like a tutorial written before Astro 5 — and wouldastro check/astro syncactually catch the difference, or only a real build? - Which lesson explains it: Content Collections for the
loader/schemashape and the realentry.render()runtime error; Dynamic Routes forgetStaticPaths()and its own real, named build error; Data Fetching for whyAstro.glob()is a type error now, not a style choice; Schemas & References for the real Zod validation error a schema/frontmatter mismatch produces.
4. Server-side Astro
Section titled “4. Server-side Astro”Astro’s server-side surface — Actions, endpoints, middleware, sessions — looks like ordinary function calls from inside the codebase, which is exactly what makes it easy to forget that most of it is also a public network endpoint the moment it’s on-demand.
- What to look for: does an Action’s handler check
context.locals.user(or an equivalent) as its first line, or does it assume the caller is authenticated because the form only renders behind a login? Does middleware always call (and return)next()on every path through the function? After a login, is the session ever regenerated? - The question to ask: if I
curlthis exact endpoint directly, with no session cookie at all, what happens — and is that answer the same one the UI’s own gating implies? - Which lesson explains it: Actions for the real, proven 401 a missing auth check produces; Middleware & Sessions for
sequence()ordering,context.locals, and session fixation; Endpoints & API Routes for the same public-reachability rule applied to a plain endpoint file.
5. Security
Section titled “5. Security”Two Astro-specific security failure modes recur constantly in generated code, and neither one is a compiler error: raw HTML injection, and a secret that crosses from server-only code into something the browser actually receives.
- What to look for: does
set:htmlever render a string that traces back to user input, without a real sanitizer in between? Does a value read fromimport.meta.env/astro:env/serverever get handed to a client-hydrated island as a prop — even if the module it came from is genuinely server-only? - The question to ask: if this exact value ends up in the page’s source (view-source, not just server logs), is that acceptable — and does anything in this code path actually stop that from happening, or does it only look like it does?
- Which lesson explains it: Astro Syntax Deep Dive for the real XSS payload
set:htmlexecutes, proven with the Container API; SSR & Adapters for whatastro:env’scontext/accessboundary actually enforces — and, just as important, what it doesn’t.
6. Performance & caching
Section titled “6. Performance & caching”Astro gives you several independent levers that all sound like the same lever — caching headers, route caching config, image optimization, shared vs. duplicated scripts, persisted DOM across a navigation — and generated code constantly assumes one of them does another one’s job.
- What to look for: does
routeRules/cacheget treated as if it sets the browser-facingCache-Controlheader? Isis:inlineused on a script that was meant to run once, shared, per page? Does a remote<Image>with hardcodedwidth/heightassumeimage.domainsis protecting it? Doestransition:persistappear on an element that has no guaranteed matching counterpart on the destination page? - The question to ask: trace this specific header, script tag, or persisted node all the way to what a real browser receives — is it actually the mechanism the code assumes it is, or a different, similarly-named one?
- Which lesson explains it: Caching & Edge for the real, proven split between
Astro.response.headersandrouteRules/cache; Images & Assets for exactly whereimage.domainsis (and isn’t) enforced; View Transitions fortransition:persistmatching and the renamed<ClientRouter />.
7. Tooling & production
Section titled “7. Tooling & production”Last, check whether the surrounding project setup — config keys, the command used to “test” a production build — is itself current, since a generated snippet can be locally correct and still sit inside a stale or misleading setup.
- What to look for: does
astro.config.mjssetmarkdown.remarkPlugins/rehypePlugins/gfmas top-levelmarkdown.*keys? Isastro previewever treated as (or scripted to run as) the actual production server? - The question to ask: does the installed Astro version’s config schema still accept this key the way the snippet assumes — and is the command being used to “prove this works in production” actually the one Astro’s own docs say is safe to use that way?
- Which lesson explains it: Config & Integrations for the deprecated top-level Markdown keys; Testing & Tooling for
astro check’s exit codes and whereeslint-plugin-astrofits.
What this module covers next
Section titled “What this module covers next”- The AI Bug Catalog — 22 specific patterns, each with the code AI typically writes, the real error/warning/behavior it produces, and the fix.
- Review Checklist — the same 22 patterns as a one-page, seven-group checklist.
- Verification Tools — what
astro check,astro build, the Container API,eslint-plugin-astro, and this course’s own harness each actually prove. - Three worked reviews — Worked Review 1, Worked Review 2, Worked Review 3 — applying this reading order to full, AI-generated files.
Mental Model
Section titled “Mental Model”Reading order for an Astro file you did not write:
1. Rendering mode & adapter → when does this actually run, and does the config agree?2. Islands & client directives → what ships to the browser, and can every prop survive the trip?3. Content & data → is this the current Content Layer API, or pre-v5 shaped?4. Server-side Astro → if I curl this directly, with no session, what happens?5. Security → does raw HTML or a secret ever cross a trust boundary?6. Performance & caching → is this lever the one the code assumes, or a similarly-named other one?7. Tooling & production → is the config current, and is "preview" being used as "production"?
Skipping straight to "does astro check pass" answers almost none of these — most of thismodule's catalog passes astro check with zero errors or warnings.Common Misconceptions
Section titled “Common Misconceptions”- “If
astro checkpasses, the code is correct.” Most entries in this module’s catalog passastro checkclean — it type-checks.astro/.ts/.tsx, it does not execute a page, run a build, or curl an endpoint. - “AI-generated Astro just needs a newer prompt.” The problem isn’t staleness alone —
output: 'hybrid',Astro.glob(), and<ViewTransitions />are specifically fluent-looking, syntactically plausible Astro that happens to target a version this course’s installed 7.3.5 no longer has. - “A silent failure is rarer than a build error, so it matters less.” The opposite is closer to true in this catalog: a leaked secret in an island’s serialized props, or a
transition:persistthat silently stops persisting, ships to production with zero errors, zero warnings, and zero indication anything is wrong. - “Astro’s rendering model is simple, so there’s less to review than in a client framework.” Astro adds a whole axis client frameworks don’t have at all — when a piece of code runs (build time vs. request time) changes what every API in this catalog is even allowed to do.