Astro Adapter
Astro with islands architecture, zero-JS-by-default content rendering, MDX support, and SSR via Lunx. Ideal for content-heavy sites that need blazing-fast Core Web Vitals.
Only interactive components ship JavaScript. Static content is pure HTML — zero JS overhead.
Write content in MDX files with full component support. Content collections provide type-safe frontmatter.
Mix React, Vue, Svelte, and Solid components on the same page. Each island hydrates independently.
Type-safe content schemas with Zod validation for blog posts, docs, products, and any structured content.
Per-page SSR or static pre-rendering. Hybrid mode renders some pages statically, some dynamically.
Zero layout shift, minimal LCP — Astro sites routinely score 100 on Lighthouse performance.
Setup
npm create lunx-dev@latest my-astro-site -- --framework astro --ts cd my-astro-site && npm install && npm run dev
Astro component syntax
--- // Frontmatter script — runs on the server at build time import type { GetStaticPaths } from 'astro' import { getCollection } from 'astro:content' import Layout from '@/layouts/BlogLayout.astro' import { Counter } from '@/components/Counter.tsx' // React island // Generate static pages for all blog posts export const getStaticPaths: GetStaticPaths = async () => { const posts = await getCollection('blog') return posts.map(post => ({ params: { slug: post.slug }, props: { post }, })) } const { post } = Astro.props const { Content } = await post.render() --- <Layout title={post.data.title}> <h1>{post.data.title}</h1> <time>{post.data.date.toLocaleDateString()}</time> <!-- MDX content renders as static HTML — zero JS --> <Content /> <!-- React component hydrates as an island --> <!-- client:load = hydrate immediately on page load --> <Counter client:load initialValue={0} /> <!-- client:visible = hydrate when scrolled into view --> <RelatedPosts posts={post.data.related} client:visible /> </Layout>
Content collections
import { defineCollection, z } from 'astro:content' const blog = defineCollection({ type: 'content', // MDX or Markdown files schema: z.object({ title: z.string(), description: z.string().max(160), date: z.coerce.date(), author: z.string(), tags: z.array(z.string()).default([]), draft: z.boolean().default(false), heroImage: z.string().optional(), }), }) export const collections = { blog }
Hydration directives
| Directive | When JS loads |
|---|---|
| client:load | Immediately on page load — for above-the-fold interactive components |
| client:idle | When the browser is idle after initial load — for secondary interactions |
| client:visible | When the component scrolls into viewport — for below-the-fold content |
| client:media | When a CSS media query matches — for responsive interactive islands |
| client:only | Client-side only — skips SSR entirely (use for browser-API-dependent components) |
client:* to a component. A page with 10 components but no client:* directives produces pure HTML — no hydration bundle, no runtime overhead.