Skip to content
DDelTech MUNDocs

Project structure

Route groups, colocation rules, and where a new file belongs.

src/
  app/
    (admin)/        ADMIN or MAINTAINER
    (author)/       AUTHOR, ADMIN, MAINTAINER
    (docs)/         public, this site
    (marketing)/    public
    (public)/       public, pages self-gate
    (recruitment)/  per-cycle council access
    (registerer)/   REGISTERER
    api/            16 route handlers
    globals.css     every design token
    layout.tsx      html, fonts, providers
  components/
    ui/             shadcn base-nova primitives
    theme/          theme toggle and sync
    recruitment/    shared recruitment pieces
  content/
    strings.ts      every user-visible string
    contentSchema.ts
  emails/           React Email templates
  generated/prisma/ generated, gitignored
  lib/              domain logic
    media/ payments/ realtime/ recruitment/ schemas/
  mdx-components.tsx  required by @next/mdx
  photos/           .webp imported as modules
  proxy.ts          Next 16 middleware
prisma/             schema, migrations, seeds
scripts/            37 check-*.ts plus tools
deploy/             compose.yml, Caddyfiles, deploy and backup scripts
Dockerfile          the production image
.github/workflows/  CI, deploy, health, cron, staging migrate and seed
docs/               internal engineering runbooks (AWS, CI, staging)

Route groups

Parentheses group routes without affecting URLs. Each group's layout.tsx carries its own access check, so authorization is a property of the group rather than something each page remembers.

src/proxy.ts is a coarse gate over five prefixes. The real check is always in the layout. /recruitment is the clearest case: the proxy only requires some session, and the layout does the per-cycle capability check.

Colocation

Inside a route segment:

FolderHolds
_components/Components used only by this segment
_hooks/Hooks used only by this segment
_lib/Helpers used only by this segment
actions.tsServer actions for this segment

The leading underscore keeps a folder out of routing. There are 28 files with "use server", almost all colocated this way.

Where a new file goes

  • Used by one route? In that route's _components/.
  • Used by two routes in the same group? The group's own _components/.
  • Used across groups? src/components/.
  • Domain logic with no JSX? src/lib/.
  • A zod schema? src/lib/schemas/.
  • User-visible text? src/content/strings.ts. Always.
  • Something the box needs? deploy/. It is synced to /srv/mun on every deploy.

Do not create a shared abstraction for a single caller. Leave code beside its only user until a second one exists.

Path alias

@/* maps to src/*. Use it.

Not in the image

.dockerignore excludes docs/, deploy/, .github/, root-level *.md files and every .env*. The documentation site is not affected: its pages are .mdx under src/, and both exclusions are anchored at the repository root.