# Pages

## Pages

**Skill level:** Beginner to edit, light code to add a page made of sections

Every page has two parts: a **route file** in `src/pages/[...lang]/`, which decides which sections the page shows and in what order, and a **content file** in `src/content/`, which holds its title, SEO fields, page header, and named blocks. You edit the content file. You only touch the route to add, remove, or reorder sections.

### Every Page And Its File
Paths below are for English. French uses the same files in `french/`, served under `/fr/`, and Arabic in `arabic/`, served under `/ar/`.

| Page                 | URL                                   | Route (`src/pages/[...lang]/`) | Content (`src/content/`)                    |
| -------------------- | ------------------------------------- | ------------------------------ | ------------------------------------------- |
| Solo designer home   | `/`                                   | `index.astro`                  | `homepage/english/-index.md`                |
| Studio home          | `/home-studio/`                       | `home-studio.astro`            | `homepage/english/home-studio.md`           |
| Photographer home    | `/home-photo/`                        | `home-photo.astro`             | `homepage/english/home-photo.md`            |
| Projects             | `/projects/`                          | `projects/index.astro`         | `projects/english/-index.md`                |
| Project or photo series | `/projects/<entry>/`               | `projects/[single].astro`      | `projects/english/<entry>.md`               |
| Archive              | `/archive/`                           | `archive.astro`                | `pages/english/archive.mdx`                  |
| Services             | `/services/`                          | `services/index.astro`         | `services/english/-index.md`                |
| Service page         | `/services/<entry>/`                  | `services/[single].astro`      | `services/english/<entry>.md`               |
| Pricing              | `/pricing/`                           | `pricing.astro`                | `pricing/english/-index.md`                 |
| FAQ                  | `/faq/`                               | `faq.astro`                    | `faq/english/-index.md`                     |
| About                | `/about/`                             | `about.astro`                  | `about-us/english/-index.md`                |
| Team                 | `/team/`, `/team/<member>/`           | `team/`                        | `team/english/`                             |
| Testimonials         | `/testimonials/`                      | `testimonials.astro`           | `testimonials/english/-index.md`            |
| Journal              | `/blog/`, posts, `/blog/category/<name>/`, `/blog/tag/<name>/`, `/blog/rss.xml` | `blog/` | `blog/english/`, authors in `author/english/` |
| Contact              | `/contact/`                           | `contact.astro`                | `contact/english/-index.md`                 |
| Legal and text pages | `/privacy-policy/`, `/terms-conditions/`, `/cookie-policy/` | `[page].astro` | `pages/english/*.md`              |
| Shortcode demo       | `/components/`                        | `[page].astro`                 | `pages/english/components.mdx`              |
| Layout gallery       | `/elements/`                          | `elements.astro`               | `pages/english/elements.mdx`                 |
| 404                  | any missing URL                       | `404.astro`                    | `sections/english/not-found-section.md`     |

The homepages are explained in [Homepages](/margo/customization/homepages/), projects and the archive in [Projects](/margo/customization/projects/), and the other collections in [Collections](/margo/customization/collections/).

### Named Blocks
A page file changes the sections it shows with **named blocks**: `pageHeader:`, `projectsSection:`, `faqSection:`, `ctaSection:`, and so on. A block is merged over the section's own file in `src/content/sections/<language>/`:

- Objects merge key by key, so a block only needs the fields it changes.
- Lists replace the whole list: repeat every item you want to keep.
- An empty string (`""`) clears a value.

```yaml
# src/content/pages/english/archive.mdx
projectsSection:
  title: "" # no heading row on this page
  options:
    layout: "index" # the other options keep their section defaults
    order: "year"
```

Copy block names from `sectionsSchema` in `src/sections.schema.ts`. A misspelled block name, such as `teamSection:` instead of `teamGridSection:`, is ignored without an error. A `contactSection` block in a page file that has a schema needs `title`, `description`, and `form`. See [Sections](/margo/customization/sections/).

**The call to action.** Inner pages show the call to action section above the footer. Change it on one page with a `ctaSection:` block, or hide it there with `ctaSection: { enable: false }`. The solo designer homepage doesn't show it.

### Page Header
Inner pages start with a page header. It is set by the `pageHeader` block in the page's content file. The title is `pageHeader.title`, or the page's `title` when that is empty.

```yaml
# src/content/services/english/-index.md
pageHeader:
  title: "Five ways to *work* together" # one line; *word* is set in the serif accent
  breadcrumb: false
  eyebrow: "What I do" # a small pill above the title
  description: "**From a first mark to a full season of pictures.** Each service starts with a short call."
  primaryButton:
    enable: true
    label: "Start a project"
    url: "/contact/"
  secondaryButton:
    enable: true
    label: "See pricing"
    url: "/pricing/"
    variant: "outline"
  options:
    layout: "centered" # centered | left | split
    titleSize: "display" # display (large) | title (smaller)
```

`buttons` adds more buttons. In the `split` layout, `image` and `imageAlt` put a portrait on the right, as on `/contact/`. In `description`, `**bold**` text is set darker than the rest of the line. Project, service, team member, journal post, and text pages use the `left` layout unless their own `pageHeader` block sets another one. `pageHeaderSchema` in `src/sections.schema.ts` lists every field.

### Fields Every Page Accepts
These frontmatter fields work on page and entry files. `src/content/_file_format.md` explains most of them; the full list is the `page` schema in `src/content.config.ts`.

| Field                          | What it does                                                         |
| ------------------------------ | -------------------------------------------------------------------- |
| `title`, `description`         | The page title and summary                                           |
| `metaTitle`, `metaDescription` | Replace the title and description in search results only             |
| `image`                        | The cover or social preview image of the page                        |
| `date`, `updated`              | Publish date and last edit. Text pages show "Last updated"           |
| `draft`                        | `true` leaves the page out of the built site. It still shows in `npm run dev` |
| `customSlug`                   | A different URL segment than the file name. Use the same value in every language |
| `canonical`, `robots`, `keywords` | SEO overrides                                                     |
| `excludeFromSitemap`, `excludeFromSearch`, `excludeFromAi` | Leave the page out of the sitemap, site search, or the AI files |
| `disableTagline`               | Don't add the site tagline to this page's title                      |
| `headerLayout`                 | Another header layout on this page: `pill`, `bar`, or `overlay`      |
| `footerLayout`                 | Another footer layout on this page: `wordmark`, `columns`, or `email` |
| `contactDock`                  | `true` or `false` shows or hides the contact dock on this page. See [Contact & Booking](/margo/customization/contact-booking/) |

`draft: true` in a collection's `-index.md` file leaves out the list page and every entry of that collection.

### Text Pages
Every file in `src/content/pages/<language>/` becomes a page at `/<file name>/`, rendered by `src/pages/[...lang]/[page].astro`, except `archive.mdx` and `elements.mdx`, which have their own route files. The legal pages are built this way.

```yaml
# src/content/pages/english/privacy-policy.mdx
title: "Privacy policy"
description: "What I collect when you write to me..." # the line under the title
date: 2026-10-01 # shown as "Last updated" (or set `updated`)
pageHeader:
  options:
    layout: "left"
contentPage:
  notice: "**A template to adapt, not legal advice.** ..." # a note above the text
```

The Markdown body is the page text. Each `##` heading joins the table of contents beside the text (above it on phones), when the page has two or more. `contentPage.notice` is a highlighted note above the text, in Markdown.

<Aside type="caution" title="Replace the legal pages">
  `privacy-policy.mdx`, `terms-conditions.mdx`, and `cookie-policy.mdx` are
  templates for a small design practice. Each says so in its
  `contentPage.notice`. Rewrite them for your business, have them checked
  against the law that applies to you, and remove the notice.
</Aside>

### Add A Page
<Tabs>
  <TabItem label="A text page (no code)">

    For policies, guides, or any page of text.

    <Steps>

    1. Copy `src/content/pages/english/terms-conditions.mdx` to a new file, for example `src/content/pages/english/licensing.mdx`. The file name becomes the URL: `/licensing/`.

    2. Change `title`, `description`, and the Markdown body. Remove `contentPage.notice`, or change it to your own note.

    3. Create the French and Arabic versions in `src/content/pages/french/` and `src/content/pages/arabic/` with the **same file name**.

    4. Add a link to it in each `src/config/menu.<language>.json` file. See [Menus](/margo/customization/menus/).

    </Steps>

  </TabItem>
  <TabItem label="A page made of sections (light code)">

    For a page that reuses Margo's sections. The archive page is the simplest model: a page header and one section.

    <Steps>

    1. Copy `src/pages/[...lang]/archive.astro` to `src/pages/[...lang]/process.astro`. The file name becomes the URL: `/process/`.

    2. In the copy, change `getEntryCTM("pages", "archive", ...)` to `getEntryCTM("pages", "process", ...)`, and create `src/content/pages/english/process.mdx` with a `title`, a `pageHeader` block, and the named blocks you need. Add `"process"` to the `ownRoutes` list in `src/pages/[...lang]/[page].astro`, so the text-page route doesn't build it a second time.

    3. Replace the sections inside `<Base>` with the ones you want, import each one from `src/layouts/components/sections/`, and pass each its block: `<StepsSection content={entry?.data?.stepsSection} />`.

    4. Create the content file in the `french/` and `arabic/` folders, and add the page to the menus.

    5. Run `npm run astro-check`.

    </Steps>

    An AI assistant can do this for you. `SKILLS.md` in the theme has an "Add a new page" recipe.

  </TabItem>
</Tabs>

### Remove A Page
1. Delete its route file in `src/pages/[...lang]/` and its content (file or folder) in `src/content/`, in every language. A text page has only the content file.
2. Remove its links from each `src/config/menu.<language>.json` file and from any content file that links to it (search the project for the URL).
3. Run `npm run astro-check`, then `npm run build`, to make sure nothing still points to it.

To hide a page without deleting it, set `draft: true` in its content file, in every language.

<Aside type="note">
  The main homepage (`index.astro`) doesn't read `draft`. The studio and
  photographer homepages do, but to remove a demo homepage you don't need,
  delete its files and its menu links instead. See
  [Homepages](/margo/customization/homepages/).
</Aside>

### Do It With AI
```text
Add a page at /[URL]/ made of existing Margo sections: [SECTIONS, IN ORDER]. Copy
src/pages/[...lang]/archive.astro as the route, read a new content file
src/content/pages/english/[NAME].md, and add "[NAME]" to ownRoutes in
src/pages/[...lang]/[page].astro. Put all copy in the content file as named blocks (block names
from sectionsSchema in src/sections.schema.ts). Create the french and arabic content files with
translated copy, add the page to each src/config/menu.<language>.json, then run
npm run astro-check.
```