Skip to main content
mf² follows a colocation-first approach: components, logic, and related files live inside the route folders that use them. This matches the Next.js App Router design and keeps features self-contained. An agent modifying a feature finds everything in one folder.

The Principle

Place files next to the code that uses them. Route-specific components go in a components/ folder inside their route group. Shared components go in the nearest common parent. Globally shared code lives in packages/. This is the layout of the main app:
apps/app/app/
├── (authenticated)/
│   ├── components/           # Components for authenticated routes
│   │   ├── header.tsx
│   │   ├── sidebar.tsx
│   │   └── search.tsx
│   ├── layout.tsx
│   ├── page.tsx
│   ├── search/
│   │   └── page.tsx
│   └── webhooks/
│       └── page.tsx
├── (unauthenticated)/
│   ├── layout.tsx
│   ├── sign-in/
│   └── sign-up/
├── actions/                  # Server actions
├── api/                      # Route handlers
└── layout.tsx                # Root layout wraps everything
A components/ folder never becomes a route because it contains no page.tsx. Next.js only creates routes for folders with a page or route file, so component files can sit inside the router tree without affecting URLs.

Where Things Live

ScopeLocationExample
Route groupapp/(authenticated)/components/The sidebar and header for authenticated pages
Single appapps/web/components/A component used across one app’s routes
All appspackages/design-system/Buttons, cards, inputs from @repo/design-system
Move a component up only when a second consumer needs it. Start colocated, promote when reuse is proven.

Route Groups

Route groups wrap related routes without affecting the URL. Parenthesized folder names like (authenticated) and (unauthenticated) organize code while keeping paths clean:
apps/app/app/
├── (authenticated)/     # /, /search, /webhooks (no "(authenticated)" in URL)
│   ├── page.tsx
│   ├── search/
│   └── webhooks/
└── (unauthenticated)/   # /sign-in, /sign-up
    ├── sign-in/
    └── sign-up/
Each group has its own layout.tsx for shared UI: the authenticated layout renders the sidebar and providers, the unauthenticated layout wraps the auth forms.

Server and Client Boundaries

Keep "use client" at the leaf level. Page files and layouts run as Server Components by default. Push client interactivity (state, events, hooks) into the components/ folder:
apps/app/app/(authenticated)/page.tsx
import { Header } from "./components/header";

export default async function App() {
  const { orgId } = await auth(); // Server: fetch data
  return <Header page="Home" pages={[]} />; // Client: render interactive UI
}
apps/app/app/(authenticated)/components/search.tsx
"use client";

export function Search() {
  const [query, setQuery] = useState("");
  // Client-side interactivity lives here
}

Colocating Non-Component Files

Routes can colocate more than components. Server actions and route handlers live inside the same app/ tree:
apps/app/app/
├── actions/
│   └── users/
│       ├── get.ts            # Server actions for user data
│       └── search.ts
└── api/
    └── collaboration/
        └── auth/
            └── route.ts      # Liveblocks auth endpoint
When a helper is reused across multiple routes, move it up to the app root or to a shared package.

In a Monorepo

mf² splits shared code into packages. The colocation hierarchy extends across the monorepo:
  1. Route group components/: shared by routes in one group
  2. App-level components/: shared across routes in one app
  3. @repo/design-system: shared across all apps
  4. Other @repo/* packages: shared utilities, hooks, configs
This keeps each layer focused. A component in @repo/design-system serves every app. A component in (authenticated)/components/ serves one route group. The hierarchy tells an agent where to put new code. Route-specific component: the route group’s components/ folder. Shared across apps: @repo/design-system. Convention replaces guesswork.