Subdomain Model

Grove projects can contain multiple apps, each served on its own subdomain. In development, <app>.localhost:<port> routes to the corresponding apps/<app>/ directory. In production, routing uses the X-Tenant-ID header set by your reverse proxy.

Prerequisites: File-Based Routing, Server Configuration. What you'll learn: How multi-app projects work, the tenant ID format, dev-mode subdomain routing, and single-app defaults.

App Discovery

Grove scans the apps/ directory at startup. Each subdirectory becomes a separate app with its own pages, layouts, and API routes:

apps/
  main/            # app "main"
    layout.html
    index.html
  docs/            # app "docs"
    layout.html
    index.html
  admin/           # app "admin"
    layout.html
    index.html

Each app is fully independent -- it has its own routing tree, layouts, static assets, and data loaders.

Tenant ID Format

Each app gets a tenant ID composed of the project ID and app name:

{project_id}-{app_name}

For a project with project_id = "grove-docs", the tenant IDs would be:

App directoryTenant ID
apps/main/grove-docs-main
apps/docs/grove-docs-docs
apps/admin/grove-docs-admin

Dev Mode Routing

In development, Grove maps subdomains of localhost to apps:

URLRoutes to
main.localhost:3000apps/main/
docs.localhost:3000apps/docs/
admin.localhost:3000apps/admin/
localhost:3000Embedded dev console

Bare localhost:<port> (without a subdomain) serves the built-in developer console, which lists available apps and provides project-level tooling.

Production Routing

In production, a reverse proxy (e.g., Nginx, Caddy, or a CDN) sets the X-Tenant-ID header based on the incoming hostname. Grove's tenant_dispatch() reads this header to route the request to the correct app.

Example Nginx configuration:

location / {
  proxy_set_header X-Tenant-ID grove-docs-main;
  proxy_pass http://localhost:3000;
}

Manzano-hosted edge deployments set X-Tenant-ID automatically, alongside the richer identity-plus-routing header set used for authentication and authorization. See Identity Flow for the complete list of headers the edge stamps on every request (including X-Site-Id, X-Project-Id, X-Distribution-Id, and X-Sub), and the HMAC signature that lets your backend trust them.

Single-App Default

When a project contains only one app, that app becomes the default. It serves all requests without requiring a subdomain or X-Tenant-ID header:

apps/
  main/          # only app -- serves on all requests
    layout.html
    index.html

In this case, both localhost:3000 and main.localhost:3000 serve the same app. The dev console is not shown.

Multi-App Project Structure

A typical multi-app project shares Grove modules across apps while giving each app its own web layer:

grove.toml
orders/
  module.grove           # shared module
apps/
  storefront/
    layout.html
    index.html
    api/orders/route.grove
  admin/
    layout.html
    index.html
    api/orders/route.grove

Both apps can reference the same orders module in their API routes, but each has independent page templates and layouts.

See Also