Project Structure

Catmint uses a convention-based project layout. This page describes the standard directory structure, file naming conventions, and routing patterns.

Directory Layout

A typical Catmint project has the following top-level structure:

my-app/
  app/                    # Application source (routes, layouts, pages)
    page.tsx              # Root page (renders at /)
    layout.tsx            # Root layout (wraps all pages)
    public/               # Static assets (served as-is)
      favicon.ico
      robots.txt
    about/
      page.tsx            # Renders at /about
    blog/
      page.tsx            # Renders at /blog
      layout.tsx          # Layout for all /blog/* pages
      [slug]/
        page.tsx          # Renders at /blog/:slug
  catmint.config.ts         # Framework configuration
  tsconfig.json           # TypeScript configuration
  package.json

The app/ directory is the root of your application. All routes, layouts, middleware, and endpoints are defined by the files placed inside it. The app/public/ directory holds static assets that are served without processing.

File Conventions

Catmint recognizes specific filenames within the app/ directory. Each convention serves a distinct role in the framework.

Core Files

FilePurpose
page.tsxDefines the UI for a route segment. The default export is a React component rendered when the route matches.
layout.tsxWraps child pages and nested layouts. Receives a children prop. Persists across navigation between sibling routes.
loading.tsxDisplayed as a fallback while the page component or its data is loading. Used with streaming SSR and Suspense.
error.tsxRenders when an error occurs within the route segment. Acts as a React error boundary for the segment and its children.
middleware.tsIntercepts requests before they reach the page. Executes using an onion model. Inherited by child routes unless inherit: false is set.
endpoint.tsDefines API route handlers. Exports named functions matching HTTP methods (GET, POST, PUT, DELETE, etc.).

Special Files

FilePurpose
404.tsxCustom "Not Found" page. Rendered when no route matches the requested URL. Place in the app/ root.
500.tsxCustom "Internal Server Error" page. Rendered on unhandled server errors. Place in the app/ root.

File Suffixes

Catmint uses file suffixes to control where code runs and how it is bundled:

SuffixBehavior
*.fn.tsServer function files. Exports are compiled into RPC endpoints that can be called from client components via createServerFn.
*.server.tsServer-only modules. Excluded from the client bundle entirely. Importing from client code will produce a build error.
*.client.tsxClient-only modules. These files are never executed on the server and are hydrated on the client.

Routing Patterns

Static Routes

Each directory inside app/ that contains a page.tsx becomes a route. The path is determined by the directory structure:

app/page.tsx             -> /
app/about/page.tsx       -> /about
app/blog/page.tsx        -> /blog
app/settings/page.tsx    -> /settings

Dynamic Segments

Wrap a directory name in square brackets to create a dynamic segment. The matched value is available via useParams() from catmint/hooks:

app/blog/[slug]/page.tsx          -> /blog/:slug
app/users/[id]/page.tsx           -> /users/:id
app/shop/[category]/[item]/page.tsx -> /shop/:category/:item
// app/users/[id]/page.tsx
import { useParams } from 'catmint/hooks'

export default function UserPage() {
  const { id } = useParams()
  return <h1>User {id}</h1>
}

Catch-All Segments

Use the spread syntax inside brackets to match any number of path segments:

app/docs/[...path]/page.tsx       -> /docs/*

The parameter value will be an array of strings. For example, /docs/guides/routing/dynamic results in path being ["guides", "routing", "dynamic"].

// app/docs/[...path]/page.tsx
import { useParams } from 'catmint/hooks'

export default function DocsPage() {
  const { path } = useParams()
  return <p>Path segments: {path.join(' / ')}</p>
}

Optional Catch-All Segments

Double brackets make the catch-all optional, meaning the route also matches the parent path without any additional segments:

app/docs/[[...path]]/page.tsx     -> /docs and /docs/*

When accessed at /docs with no trailing segments, path will be an empty array.

Route Groups

Directories wrapped in parentheses create route groups. They organize files without adding a segment to the URL path:

app/(marketing)/page.tsx          -> /
app/(marketing)/pricing/page.tsx  -> /pricing
app/(dashboard)/settings/page.tsx -> /settings

Route groups are useful for applying different layouts to different sections of your application without affecting the URL:

app/
  (marketing)/
    layout.tsx          # Marketing layout
    page.tsx            # Renders at /
    pricing/
      page.tsx          # Renders at /pricing
  (dashboard)/
    layout.tsx          # Dashboard layout (different from marketing)
    settings/
      page.tsx          # Renders at /settings
    profile/
      page.tsx          # Renders at /profile

Configuration

The catmint.config.ts file at the project root defines framework behavior. It uses the defineConfig helper from catmint/config:

// catmint.config.ts
import { defineConfig } from 'catmint/config'
import node from '@catmint/adapter-node'

export default defineConfig({
  mode: 'fullstack',
  adapter: node({ port: 3000 }),
})

The three modes control what Catmint includes in the build:

ModePagesAPI EndpointsServer Functions
fullstackYesYesYes
frontendYesNoNo
backendNoYesYes

Next Steps

  • Routing -- detailed routing guide with advanced patterns
  • Layouts -- nested layouts and layout boundaries
  • Middleware -- request interception and the onion model
  • API Endpoints -- building HTTP handlers with endpoint files
  • Server Functions -- RPC-style server calls from client components