Skip to content
New: Customize your theme with an AI assistant. Read the guide

How Ecozen Works

Skill level: Everyone · Time: 10 minutes

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

The Big Picture

Ecozen 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. That describes the Astro edition. The Tina edition adds a visual editor at /admin/ for the same files, and the Sanity edition keeps content in your own Sanity project: see Editions. All three build static pages.

config.toml ──► site settings: name, URL, contact details, forms
menu.en.json ─► header and footer links
theme.css ────► colors, type scale, breakpoints
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/pricing-section.md
├─ + page block: pricingSection: { ... }
└─ look: options.layout (some sections)
Base.astro wraps every page:
head · header · page · footer · 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:

<Base {...data}>
<PageHeader content={data} />
<PricingSection content={data.pricingSection} />
<BenefitsSection content={data.benefitsSection} />
<FaqSection content={data.faqSection} />
<CTASection content={data.ctaSection} />
</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/, Arabic at /ar/pricing/.

2. Sections Have Default Content

Every section has a matching content file in src/content/sections/<language>/. For example, PricingSection.astro reads pricing-section.md. That file holds the section’s default text, lists, buttons, and options, often with comments that explain them.

Edit a section file and the change appears everywhere that section is used. 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. The pricing page uses this to hide the section heading, because the page header already shows one:

src/content/pricing/english/-index.md
---
title: "Solar packages with a fixed price"
pricingSection: # overrides src/content/sections/english/pricing-section.md
badge: ""
title: ""
description: ""
---

Block names are the section name in camelCase: heroSection, pricingSection, faqSection, ctaSection, and so on. Most page files say in their comments which section file each block overrides. Sections has the full list.

A block with a wrong name, for example teamsSection: instead of teamSection:, is ignored without an error. Copy block names from the page file, the Sections table, or sectionsSchema in src/sections.schema.ts.

4. How Overrides Merge

When a page block meets the section’s default content, Ecozen merges them with four rules:

You write in the page blockResult
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 (list:, buttons:, stats:)Replaces the whole list. Write every item you want to keep
An empty string (title: "")Clears the value. Leave the key out to keep the default

So this block changes only the button label and keeps its URL and style:

companySection:
button:
label: "Meet the team"

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

5. Layouts Change The Look, Not The Content

Nine sections can be drawn in more than one way. The layout is one option:

options:
layout: "split" # the hero: photo | split | panel | form

Most content fields stay the same, so you can switch back and forth. A few fields belong to one layout only, for example the hero’s slides and facts (photo layout), stats (split layout), tiles (panel layout), or formCard (form layout), or the services band’s background image (band layout). Set the layout in the section file (every page) or in a page block (one page). The four homepages use different layouts of these sections, so together they double as a preview.

See 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, with its top bar or announcement strip above it, from config.toml, the page’s headerLayout, and the menu files. Only the header bar sticks while you scroll; the rows above it scroll away
  • the footer in one of four layouts, from config.toml, the page’s footerLayout, the menu files, src/config/social.json, and src/i18n/<language>.json
  • the cookie banner, the back-to-top button, and shared scripts

The call to action above the footer is not added by Base: each route places <CTASection /> itself, so it can pass a ctaSection block from its page file. On a page that shows it, the call-to-action band at the top of the newsletter and cta footers hides, so the page doesn’t end with two.

7. Small Interface Text Lives In i18n Files

Labels that are not page content, such as the header button (“Get a free quote”), “Read more”, the footer description and copyright, the 404 page text, and form messages, live in src/i18n/<language>.json, one file per language. 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.

8. 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 add a language, and after you change trailingSlash, defaultLanguage, or showDefaultLangInUrl: Astro reads those only when it starts.

9. What Happens During A Build

  1. config.toml is converted to JSON.
  2. Astro checks the content collections that have a schema in src/content.config.ts, 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, about-us, contact, faq, pricing, calculator, and author 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/.
  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: [forms] for a form address (while the pages with that form are built), and [site] for the https://example.com site URL (when the Astro build finishes).
  6. Draft pages are removed from the sitemap.

Development vs. Production

Some things behave differently while you work with npm run dev:

Featurenpm run devBuilt site (npm run build)
Pages with draft: trueShown, so you can preview themNot built
Changes to config.tomlShown when you save. Restart after adding a language or changing trailingSlashRead at the start of every build
Site URL warningNot shownPrinted when the Astro build finishes, while the URL is example.com
Placeholder form addressA [forms] line in the terminal, and a “Dev only · form not connected” note inside the form after you submit itA [forms] line in the build output. Visitors never see the note
Quote formSends to the address in config.toml, so test with careSame

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 Ctrl + Shift + F (⌘ + Shift + F 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)
  2. The section’s default file (src/content/sections/<language>/)
  3. A collection entry (src/content/services/, projects/, careers/, blog/, team/, testimonial/)
  4. The interface text (src/i18n/<language>.json)
  5. The menus (src/config/menu.<language>.json)
  6. Site settings (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.

Type something to search...