# How Homio Works

## How Homio Works

**Skill level:** Everyone · **Time:** 10 minutes

Read this page once and the rest of the theme stops being a mystery. Homio is built from a few simple ideas that repeat everywhere.

### The Big Picture
Homio is a **static site**: when you run `npm run build`, it turns your text files into finished HTML pages in `dist/`. There is no database and no admin panel. Your files *are* the website.

```text
config.toml ──► site settings, [business] details, forms, estimator, booking
menu.en.json ─► header and footer links
theme.css ────► colors, type scale, breakpoints
pricing/ ─────► every price: estimator, booking, plans, add-ons, "from" prices

ROUTE  src/pages/[...lang]/pricing.astro
  │    which sections, in which order
  │
  ├─ PAGE  src/content/pricing/english/-index.md
  │        title, SEO, page header, named blocks
  │
  └─ each SECTION
       ├─ default: sections/english/plans-section.md
       ├─ + page block: plansSection: { ... }
       └─ look: options.layout (many sections)

Base.astro wraps every page:
  head · header · page · call to action · footer · mobile action bar · cookie banner
```

### 1. Routes Decide What A Page Shows
Each file in `src/pages/[...lang]/` is one page (a **route**). It lists the sections that page shows, top to bottom. This is the pricing page, `src/pages/[...lang]/pricing.astro`, shortened:

```astro
<Base {...entry?.data}>
  <PageHeader content={{ ...entry?.data, currentLabel: entry?.data.title }} />
  <JumpNav items={jumpNav} />
  <LayoutVariants section="plans" id="plans" content={plansSection} />
  <EstimateSection id="estimate" content={estimateSection} />
  <AddOnsSection id="add-ons" content={addOnsSection} />
  <IncludedSection id="compare" content={includedSection} />
  <LayoutVariants section="faq" id="policies" content={questionsSection} />
</Base>
```

To **reorder** sections, move a line. To **remove** a section from one page, delete its line. The `[...lang]` folder name means one file builds every language: English at `/pricing/`, French at `/fr/pricing/`.

Sections that have more than one layout are drawn through `<LayoutVariants section="..." content={...} />`. Keep that wrapper when you move them.

### 2. Sections Have Default Content
Every section has a matching content file in `src/content/sections/<language>/`. For example, `EstimateSection.astro` reads `estimate-section.md`. That file holds the section's default text, lists, buttons, and options, with comments that explain them.

Edit a section file and the change appears **everywhere that section is used**, unless a page overrides it. Set `enable: false` at the top of the file to hide the section on every page.

### 3. Pages Can Override Any Section
A page file can change a section **for that page only** with a **named block**. Every page file in Homio uses them, the homepages included. This is the start of the hero block on the main homepage:

```yaml
# src/content/homepage/english/-index.md
---
heroSection: # defaults: hero-section.md
  enable: true
  title: "A cleaner home, the same crew every visit"
  checks: ["Insured", "Background-checked", "Fixed prices"]
  options:
    layout: "default" # default | split | form | band
---
```

Block names are the section name in camelCase: `heroSection`, `plansSection`, `questionsSection`, `ctaSection`, and so on. Most blocks have a comment that names the section file they override (`# defaults: hero-section.md`). [Sections](/homio/customization/sections/) has the full list.

A block with a wrong name, for example `faqSection:` instead of `questionsSection:`, is ignored without an error. Copy block names from the page file, the [Sections](/homio/customization/sections/) table, or `sectionsSchema` in `src/sections.schema.ts`.

<Aside type="caution" title="A block only works where the route uses it">
  Adding `areasSection:` to the pricing page does nothing, because
  `pricing.astro` does not render the areas section. Check the route file to
  see which blocks a page reads.
</Aside>

### 4. How Overrides Merge
When a page block meets the section's default content, Homio merges them with four rules:

| You write in the page block            | Result                                                         |
| -------------------------------------- | -------------------------------------------------------------- |
| A text or number (`title: "Hello"`)    | Replaces the default value                                     |
| An object (`button:`, `options:`)      | Merges key by key: only the keys you write change              |
| A list (`checks:`, `steps:`, `list:`)  | Replaces the **whole** list. Write every item you want to keep |
| An empty string (`description: ""`)    | **Clears** the value. Leave the key out to keep the default    |

So this block switches the call to action to another layout on one page and keeps all its text and buttons:

```yaml
ctaSection:
  enable: true
  options:
    layout: "split"
```

Keep `enable: true` at the top of each page block, as the shipped files do. In page files that have a schema, some blocks (the call to action, the contact section, the logo strip) count as hidden when `enable` is left out.

### 5. Layouts Change The Look, Not The Content
Many sections can be drawn in more than one way. The layout is one option:

```yaml
options:
  layout: "split" # the hero: default | split | form | band
```

The allowed values are always in the comment next to `layout:`. Most content fields stay the same, so you can switch back and forth. A few fields belong to one layout only, and their comment says so, for example the hero's `form` (form layout) or the call to action's `ticket` (split layout). Set the layout in the section file (every page) or in a page block (one page).

Open `/elements/` on your local site to see the layouts of the multi-layout sections and of the page header side by side. See [Section Layouts](/homio/customization/section-layouts/) for every value.

### 6. Base Wraps Every Page
`src/layouts/Base.astro` adds the parts that every page shares:

- the `<head>`: title, description, Open Graph tags, structured data, fonts, and your `[head]` scripts
- the header in one of four layouts (`[settings.layout] header` in `config.toml`, or `headerLayout` in a page), with the menus and the phone number from `[business]`
- the call to action above the footer, from `call-to-action.md` and the page's `ctaSection` block
- the footer in one of four layouts (`[settings.layout] footer`, or `footerLayout` in a page), from the menu files, `[business]`, `src/config/social.json`, and `src/i18n/<language>.json`
- the **Call / Get a price** bar on phones (`[settings.mobileActionBar]`), the cookie banner, the back-to-top button, and shared scripts

### 7. Business Details Are Set Once
Your company name, phone, email, address, opening hours, time zone, and closed dates live in `[business]` in `config.toml`. The header, footer, contact page, area pages, booking form, structured data, and `llms.txt` all read them from there.

Inside Markdown text and some content fields, you can write `{name}`, `{phone}`, `{email}`, `{address}`, or `{hours}`, and Homio fills in the value from `[business]`. The tokens work in `.md` files, not in `.mdx` files. Field comments say where a token works, for example `# Inline markdown and {phone} work` in `announcement-bar.md`.

### 8. Prices Come From One File
The estimator, the booking form, the plan cards, the add-ons, the trade rate sheet, and the "from" prices on service cards all compute their numbers from `src/content/pricing/<language>/-index.md`: visit frequencies with their discounts, one rate card per service, and the add-ons. You never type a price into a section. Change a rate there and every place that shows it updates. See [Pricing & Estimator](/homio/customization/pricing-estimator/).

### 9. Small Interface Text Lives In i18n Files
Labels that are not page content, such as the header button ("Get a price"), form messages, the footer description and copyright, and the 404 text, live in `src/i18n/en.json` and `src/i18n/fr.json`. Several `config.toml` labels are empty on purpose (`label = ""`): empty means "use the translated text from the i18n file". Fill them in only if your site has one language.

### 10. Config Is Converted Before Astro Reads It
Astro cannot read TOML, so `npm run dev`, `npm run build`, and `npm run astro-check` first convert `src/config/config.toml` into `.astro/config.generated.json`. While `npm run dev` is running, this happens every time you save `config.toml`, and the page reloads. Always edit the TOML file, never the generated JSON.

Restart `npm run dev` after you change `trailingSlash` or `[settings.multilingual]`, after you add a language, and after you change `src/config/fonts.json`: Astro reads those only when it starts.

### 11. Collections Hold The Repeated Content
A **collection** is a folder of similar entries, each one a file. Homio's collections are `services`, `pricing`, `areas`, `industries`, `projects`, `reviews`, `team`, `careers`, `blog`, `author`, `pages`, `homepage`, `about-us`, `contact`, `faq`, `guarantee`, and `sections`. They are defined in `src/content.config.ts`, and most have a **schema**: the list of fields an entry may have. See [Collections](/homio/customization/collections/).

### 12. What Happens During A Build
<Steps>

1. `config.toml` is converted to JSON.
2. Astro checks the content collections that have a schema, including the named blocks in their page frontmatter (`sectionsSchema` in `src/sections.schema.ts`). A wrong value there stops the build with the file name and field. The `sections` and `contact` collections have no schema, so a wrong value in those files is not reported. Check those pages in the browser.
3. Every route is rendered once per language into `dist/`. A page without its own `image` gets a generated Open Graph card.
4. `llms.txt`, `llms-full.txt`, the Markdown copies of pages, the sitemap, `robots.txt`, and the RSS feed are written.
5. Yellow warnings print for anything still a placeholder: `[site]` for the `https://example.com` site URL, `[forms]` for a placeholder form address, `[business]` for a value it could not read, and `[pricing]` when the English and French price files differ.
6. Draft pages are removed from the sitemap.

</Steps>

### Development vs. Production
Some things behave differently while you work with `npm run dev`:

| Feature                         | `npm run dev`                       | Built site (`npm run build`)          |
| ------------------------------- | ----------------------------------- | ------------------------------------- |
| Pages with `draft: true`        | Shown, so you can preview them      | Not built                             |
| Changes to `config.toml`        | Shown when you save. Restart after changing languages, `trailingSlash`, or fonts | Read at the start of every build |
| Placeholder form address        | A warning box inside the form, and a `[forms]` line in the terminal | A `[forms]` line in the build output |
| Site URL warning                | Not shown                           | Printed when the Astro build finishes, while the URL is `example.com` |
| Open Graph cards for each page  | Not generated                       | Generated during the build            |
| Forms and booking requests      | Sent to the address in `config.toml`, so test with care | Same |

### Find Where Any Text Comes From
See a sentence on the site and want to change it? Search the whole project for it: in VS Code press <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>F</kbd> (<kbd>⌘</kbd> + <kbd>Shift</kbd> + <kbd>F</kbd> on Mac) and paste a few words. It will be in one of these places, checked in this order:

1. The page's own file, in a named block (`src/content/<page>/<language>/-index.md`, or `src/content/pages/<language>/` for single pages such as `/book/`)
2. The section's default file (`src/content/sections/<language>/`)
3. A collection entry (`src/content/services/`, `areas/`, `projects/`, `reviews/`, `team/`, `careers/`, `blog/`)
4. The price file (`src/content/pricing/<language>/-index.md`) for prices, plan names, frequencies, and add-ons
5. The interface text (`src/i18n/<language>.json`)
6. The menus (`src/config/menu.<language>.json`)
7. Site settings and business details (`src/config/config.toml`)

If the same text appears in both a page block and a section file, the page block wins on that page. If you can't find a phone number, an address, or opening hours anywhere in `src/content/`, it comes from `[business]` in `config.toml`.