Astro

Making component pages editable

Many Astro themes build their main pages — home, services, pricing, about — from components, with the words written straight into the .astro file:

<Hero
  tagline="Free & open source"
  title="Websites that load fast"
  actions={[{ text: 'Get started', href: '/start', variant: 'primary' }]}
/>

Orbit edits content, not code, so those words aren’t editable as they stand. Orbit won’t rewrite your .astro files for you — every theme is different, and changing a page’s code is a developer’s call. Instead, move the words into a content entry and let the page read them. The design stays in code; the words become a form. That’s the whole of making a component page Orbitable.

1. Describe the page’s words in your content config

Add a collection for the page, with one field per piece of text. Keep the fields in content.config.ts itself — Orbit reads that file but doesn’t follow imports from your own modules, so a schema imported from elsewhere would show no fields.

// src/content.config.ts
const text = z.string().optional();
const action = z.object({
  text: z.string(),
  href: z.string(),
  variant: z.enum(['primary', 'secondary']).optional(),
});

const homePage = defineCollection({
  loader: glob({ pattern: 'home.md', base: 'src/content/pages' }),
  schema: z.object({
    hero: z.object({ tagline: text, title: text, actions: z.array(action).optional() }).optional(),
    features: z
      .object({
        title: text,
        items: z.array(z.object({ title: text, description: text })).optional(),
      })
      .optional(),
  }),
});

export const collections = { /* …your other collections… */ home: homePage };

A pattern naming a single file (home.md) makes it a fixed page: editors can change it but not add more.

2. Put the words in the entry

---
# src/content/pages/home.md
hero:
  tagline: Free & open source
  title: Websites that load fast
  actions:
    - text: Get started
      href: /start
      variant: primary
features:
  title: What you get
  items:
    - title: Fast
      description: Pages that load in a blink.
---

3. Have the page read the entry

---
import { getEntry } from 'astro:content';
import Hero from '~/components/widgets/Hero.astro';
import Features from '~/components/widgets/Features.astro';

const page = (await getEntry('home', 'home'))!.data;
---

<Layout>
  {page.hero && <Hero {...page.hero} />}
  {page.features && <Features id="features" {...page.features} />}
</Layout>

The components are unchanged: they get the same props as before, just from the entry instead of the code.

4. Tell Orbit it’s a form-only page

# orbit.config.yaml
collections:
  home:
    canAdd: false   # one fixed page
    bodyless: true  # no text editor — every word is a field

What to keep in code

Anything that’s design rather than words stays in the .astro page:

  • layout and order of sections, and which ones appear;
  • CSS classes, colours, backgrounds, spacing and animation settings;
  • decorative extras (a divider, a trailing icon).

If a title mixes words and styling — say one highlighted phrase — split it into two fields (title and titleAccent) and add the styling in the page, so editors never see class names.

Text fields can still take simple HTML (<strong>, <code>, <br>) if your components render them as HTML.

A worked example

The AstroWind business site in Orbit Extras is converted this way: the words of its home, about, services, pricing and contact pages — heroes, features, team, testimonials, prices, FAQs and the rest — are forms in Orbit, while its design is untouched. A small helper reads each page’s entry, and a shared page() function in content.config.ts keeps the five schemas short.