Routing

File-based routing in Constela

File-Based Routing

Constela uses file-based routing. Files in src/routes/ automatically become routes based on their file names.

FileRoute
index.json/
about.json/about
users/index.json/users
users/[id].json/users/:id
docs/[...slug].json/docs/*

Route Definition

Each route file includes a route field that defines metadata:

json
{
  "version": "1.0",
  "route": {
    "path": "/users/:id",
    "layout": "main",
    "meta": {
      "title": "User Profile",
      "description": "View user details"
    }
  },
  "view": { ... }
}

Route Fields

FieldRequiredDescription
pathYesURL path pattern
layoutNoLayout name to use
metaNoPage metadata (title, description)

Dynamic Routes

Path Parameters

Use [paramName] in file names for dynamic segments:

text
src/routes/
├── users/
│   └── [id].json        → /users/:id
└── posts/
    └── [year]/
        └── [slug].json  → /posts/:year/:slug

Catch-All Routes

Use [...paramName] for catch-all routes:

text
src/routes/
└── docs/
    └── [...slug].json   → /docs/*

Accessing Route Parameters

Access route parameters using the route expression:

json
{
  "kind": "text",
  "value": { "expr": "route", "name": "id" }
}

Route Expression Sources

SourceDescriptionExample
param (default)Dynamic path segments/users/:id{ "expr": "route", "name": "id" }
queryURL query parameters?search=test{ "expr": "route", "name": "search", "source": "query" }
pathFull current path{ "expr": "route", "source": "path" }

Query Parameters

Access query string values with source: "query":

json
{
  "kind": "text",
  "value": { "expr": "route", "name": "search", "source": "query" }
}

For a URL like /products?category=electronics&page=2:

json
{
  "kind": "element",
  "tag": "div",
  "children": [
    {
      "kind": "text",
      "value": { "expr": "route", "name": "category", "source": "query" }
    }
  ]
}

Navigation

Link Element

Use anchor elements for navigation:

json
{
  "kind": "element",
  "tag": "a",
  "props": {
    "href": { "expr": "lit", "value": "/about" }
  },
  "children": [
    { "kind": "text", "value": { "expr": "lit", "value": "About Us" } }
  ]
}

Programmatic Navigation

Navigate programmatically using the navigate action step:

json
{
  "actions": [
    {
      "name": "goToUser",
      "steps": [
        {
          "do": "navigate",
          "url": { "expr": "lit", "value": "/users/123" }
        }
      ]
    }
  ]
}

With dynamic paths:

json
{
  "actions": [
    {
      "name": "viewUserProfile",
      "steps": [
        {
          "do": "navigate",
          "url": {
            "expr": "bin",
            "op": "+",
            "left": { "expr": "lit", "value": "/users/" },
            "right": { "expr": "state", "name": "selectedUserId" }
          }
        }
      ]
    }
  ]
}

Route Lifecycle Hooks

Execute actions when entering or leaving routes:

json
{
  "lifecycle": {
    "onRouteEnter": "loadPageData",
    "onRouteLeave": "saveProgress"
  }
}

Available Route Hooks

HookDescription
onRouteEnterExecuted when navigating to this route
onRouteLeaveExecuted when navigating away from this route

Example: Load Data on Route Enter

json
{
  "version": "1.0",
  "route": {
    "path": "/users/:id"
  },
  "lifecycle": {
    "onRouteEnter": "loadUserData"
  },
  "state": {
    "user": { "type": "object", "initial": {} },
    "loading": { "type": "boolean", "initial": true }
  },
  "actions": [
    {
      "name": "loadUserData",
      "steps": [
        {
          "do": "set",
          "target": "loading",
          "value": { "expr": "lit", "value": true }
        },
        {
          "do": "fetch",
          "url": {
            "expr": "bin",
            "op": "+",
            "left": { "expr": "lit", "value": "/api/users/" },
            "right": { "expr": "route", "name": "id" }
          },
          "onSuccess": {
            "steps": [
              {
                "do": "set",
                "target": "user",
                "value": { "expr": "var", "name": "response" }
              },
              {
                "do": "set",
                "target": "loading",
                "value": { "expr": "lit", "value": false }
              }
            ]
          }
        }
      ]
    }
  ],
  "view": {
    "kind": "element",
    "tag": "div",
    "children": [
      {
        "kind": "if",
        "condition": { "expr": "state", "name": "loading" },
        "then": {
          "kind": "text",
          "value": { "expr": "lit", "value": "Loading..." }
        },
        "else": {
          "kind": "element",
          "tag": "h1",
          "children": [
            {
              "kind": "text",
              "value": {
                "expr": "get",
                "base": { "expr": "state", "name": "user" },
                "path": "name"
              }
            }
          ]
        }
      }
    ]
  }
}

Layouts

Use layouts to share common UI across multiple routes:

json
{
  "route": {
    "path": "/dashboard",
    "layout": "dashboard"
  }
}

Create a layout file at src/layouts/dashboard.json:

json
{
  "version": "1.0",
  "type": "layout",
  "view": {
    "kind": "element",
    "tag": "div",
    "children": [
      {
        "kind": "element",
        "tag": "nav",
        "children": [
          {
            "kind": "element",
            "tag": "a",
            "props": { "href": { "expr": "lit", "value": "/" } },
            "children": [
              { "kind": "text", "value": { "expr": "lit", "value": "Home" } }
            ]
          }
        ]
      },
      { "kind": "slot" }
    ]
  }
}

Summary

Constela's file-based routing provides:

  • Automatic routes: File structure defines URL structure
  • Dynamic parameters: Use [param] and [...param] syntax
  • Route expressions: Access params and query strings in views
  • Programmatic navigation: Navigate from actions
  • Lifecycle hooks: Load data on route enter/leave
  • Layouts: Share common UI across routes