> ## Documentation Index
> Fetch the complete documentation index at: https://docs.olonjs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Create and configure static and dynamic pages in OlonJS

> Learn how to create static and dynamic pages in OlonJS by writing page JSON files, composing sections, and configuring metadata and routes.

Every page in OlonJS starts as a JSON file in `src/data/pages/`. You compose the page by listing sections with their data and settings, and OlonJS turns that JSON into a fully rendered Next.js route. This guide covers both static pages and dynamic collection routes.

***

## Creating a static page

<Steps>
  ### Create the page JSON file

  Add a new file under `src/data/pages/`. The filename (without `.json`) becomes the URL slug. Give the page a unique `id`, set a `slug`, add `meta` for SEO, and list your `sections`.

  Here is a minimal but complete example modeled on the Inkwell `about.json` page:

  ```json src/data/pages/my-page.json theme={null}
  {
    "id": "my-page",
    "slug": "my-page",
    "meta": {
      "title": "My page — Site name",
      "description": "A short description used by search engines and link previews."
    },
    "sections": [
      {
        "id": "my-page-hero",
        "type": "page-hero",
        "data": {
          "label": "My page",
          "title": "Welcome to my page",
          "description": "A brief intro paragraph shown beneath the heading."
        },
        "settings": { "paddingTop": "xl" }
      },
      {
        "id": "my-page-cta",
        "type": "cta-banner",
        "data": {
          "title": "Ready to go?",
          "primaryCta": {
            "id": "my-page-cta-1",
            "label": "Get started",
            "href": "/",
            "variant": "primary"
          }
        },
        "settings": { "paddingBottom": "xl" }
      }
    ]
  }
  ```

  Each section needs a unique `id` (scoped to the page), a `type` matching one of the registered section components, a `data` object whose shape matches that section's schema, and a `settings` object for layout overrides.

  ### Build the project

  Run the build command to bake the page data into `public/` and compile the Next.js app:

  ```bash theme={null}
  npm run build
  ```

  ### Visit the page

  Start the dev server and navigate to your new page:

  ```bash theme={null}
  npm run dev
  ```

  Then open `http://localhost:3000/my-page` in your browser.
</Steps>

***

## Creating a dynamic (collection) page

Dynamic pages generate one URL per item in a collection. You create them by naming the file with a `[slug]` segment and adding a `collection` field to the JSON.

Create a directory under `src/data/pages/` whose name matches the collection route prefix, then add a `[slug].json` template file inside it:

```json src/data/pages/products/[slug].json theme={null}
{
  "id": "product-detail-page",
  "slug": "products/[slug]",
  "collection": { "source": "products", "paramKey": "slug" },
  "meta": {
    "title": "Product detail",
    "description": "A single product from the products collection."
  },
  "sections": [
    {
      "id": "product-detail-body",
      "type": "post-detail",
      "data": {
        "backLabel": "All products",
        "item": { "$ref": "collection:current" }
      },
      "settings": { "paddingTop": "xl" }
    }
  ]
}
```

The special `$ref` value `"collection:current"` tells OlonJS to inject the collection item matching the current URL slug. At build time the framework generates individual baked pages for every item in the referenced collection, so each product gets its own pre-rendered route at `/products/<id>`.

<Note>
  The `collection.paramKey` must match the bracket name in the filename. If the file is `[slug].json`, use `"paramKey": "slug"`. If the file is `[id].json`, use `"paramKey": "id"`.
</Note>

Here is the real dynamic page template for Inkwell posts as a reference:

```json src/data/pages/posts/[slug].json theme={null}
{
  "id": "post-detail-page",
  "slug": "posts/[slug]",
  "collection": { "source": "posts", "paramKey": "slug" },
  "meta": {
    "title": "Post — Inkwell Journal essay detail",
    "description": "A single essay from the Inkwell Journal posts collection."
  },
  "sections": [
    {
      "id": "post-detail-body",
      "type": "post-detail",
      "data": {
        "backLabel": "All posts",
        "item": { "$ref": "collection:current" }
      },
      "settings": { "paddingTop": "xl" }
    },
    {
      "id": "post-detail-related-tags",
      "type": "related-tags",
      "data": {
        "label": "Relations demo",
        "title": "Filed under",
        "emptyLabel": "This post has no topics yet.",
        "item": { "$ref": "collection:current" },
        "tags": { "$ref": "../../collections/tags/tags.json" }
      },
      "settings": {}
    }
  ]
}
```

***

## Page sections quick reference

OlonJS ships 13 built-in section types. Use the `type` field in each section entry to select one.

| `type` | Typical use |
| - | - |
| `header` | Site-wide navigation bar |
| `footer` | Site-wide footer with links |
| `hero` | Full-width above-the-fold hero with image, title, and CTAs |
| `page-hero` | Compact interior-page header with label and description |
| `posts-list` | Grid or timeline of posts pulled from the posts collection |
| `tags-list` | Card grid of tags pulled from the tags collection |
| `post-detail` | Full body rendering of a single post item |
| `related-tags` | Tag chips derived from a post's `tags` relation field |
| `tag-detail` | Header block for a single tag item |
| `tag-posts` | List of posts that carry a given tag (inverse relation) |
| `content-block` | Rich text section with label, title, paragraphs, and optional image |
| `stats-band` | Horizontal band of metric / stat tiles with icons |
| `cta-banner` | Full-width call-to-action band with primary and secondary buttons |

***

## SEO metadata

Set `meta.title` and `meta.description` on every page file. OlonJS uses these values to populate the Next.js `<head>` and expose them in the MCP manifest for AI-agent discovery.

```json theme={null}
{
  "id": "about-page",
  "slug": "about",
  "meta": {
    "title": "About Inkwell Journal and this collections demo",
    "description": "Inkwell Journal is a working OlonJS demo: posts and tags live as separate collections, related through keys on the post side and resolved at render time."
  },
  "sections": []
}
```

<Tip>
  Write `meta.description` as a standalone sentence that makes sense out of context — it appears in Google snippets, link previews, and the AI-agent site manifest.
</Tip>

Dynamic collection pages share one `meta` block in the template. If you need per-item metadata (e.g. the post title as the page title), set the value to `"collection:current"` and OlonJS will resolve it from the current item at render time. Check the section schema for the specific field names your section type supports.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.