lunx.docs
DocsFrameworksAstro

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.

Astro 4 · Islands · Zero JS
Islands architecture

Only interactive components ship JavaScript. Static content is pure HTML — zero JS overhead.

MDX content

Write content in MDX files with full component support. Content collections provide type-safe frontmatter.

Framework agnostic

Mix React, Vue, Svelte, and Solid components on the same page. Each island hydrates independently.

Content collections

Type-safe content schemas with Zod validation for blog posts, docs, products, and any structured content.

SSR + SSG

Per-page SSR or static pre-rendering. Hybrid mode renders some pages statically, some dynamically.

Core Web Vitals

Zero layout shift, minimal LCP — Astro sites routinely score 100 on Lighthouse performance.

Setup

bash
npm create lunx-dev@latest my-astro-site -- --framework astro --ts
cd my-astro-site && npm install && npm run dev

Astro component syntax

htmlsrc/pages/blog/[slug].astro
---
// 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

typescriptsrc/content/config.ts
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

DirectiveWhen JS loads
client:loadImmediately on page load — for above-the-fold interactive components
client:idleWhen the browser is idle after initial load — for secondary interactions
client:visibleWhen the component scrolls into viewport — for below-the-fold content
client:mediaWhen a CSS media query matches — for responsive interactive islands
client:onlyClient-side only — skips SSR entirely (use for browser-API-dependent components)
Zero JS by default
Astro ships zero JavaScript unless you explicitly add client:* to a component. A page with 10 components but no client:* directives produces pure HTML — no hydration bundle, no runtime overhead.