DEV Community

Takahiro Ikeuchi
Takahiro Ikeuchi

Posted on

Rethinking Architecture in the AI Era — Part 1: Repository Management

This is the first installment of a series on software product architecture for the AI age. There's no shortage of content about how fast AI can accelerate prototyping — but I believe medium-term maintainability deserves equal attention. (If this ends up being a one-off article, my apologies in advance.)

Today's topic: repository management. My conclusion upfront: go with a monorepo.


What Is a Monorepo?

For those new to the concept, a monorepo is the practice of managing multiple applications within a single repository. "Multiple apps" can mean any of the following:

  • A frontend and a backend living together
  • Full-stack apps for Service A and Service B side by side
  • Application code alongside shared libraries

A quick aside on microservices: while monorepos are sometimes discussed in that context, I don't have hands-on experience combining the two, so I'll leave that out of scope here. My instinct is that getting the most out of a monorepo requires keeping your tech stack reasonably unified — which arguably conflicts with what microservices are trying to achieve. The monorepo style I'm describing here probably isn't a great fit for microservices.

All that said, rather than dwelling on definitions, the practical answer is simple: use Turborepo.


What Is Turborepo?

Turborepo has become the de facto standard for monorepo setups in TypeScript projects. While it's technically positioned as a general-purpose build tool, in practice you reach for it when adopting a monorepo (or when you want smarter build caching).

Turborepo

Turborepo is a build system optimized for JavaScript and TypeScript, written in Rust.

favicon turborepo.dev

Here's a representative directory structure for a web frontend + backend project using Turborepo:

my-turborepo/
├── apps/
│   ├── web/                  # Frontend app (e.g. Next.js)
│   │   ├── src/
│   │   ├── package.json
│   │   └── tsconfig.json
│   └── api/                  # Backend app (e.g. Hono, Express)
│       ├── src/
│       ├── package.json
│       └── tsconfig.json
├── packages/
│   ├── ui/                   # Shared UI component library
│   │   ├── src/
│   │   ├── package.json
│   │   └── tsconfig.json
│   ├── tsconfig/             # Shared TypeScript config
│   │   ├── base.json
│   │   ├── nextjs.json
│   │   └── package.json
│   └── utils/                # Shared utilities
│       ├── src/
│       ├── package.json
│       └── tsconfig.json
├── .claude/
│   ├── CLAUDE.md
│   └── skills/
├── .codex/
│   └── skills/
├── turbo.json
├── biome.json
├── package.json
├── pnpm-workspace.yaml
└── tsconfig.json
Enter fullscreen mode Exit fullscreen mode

The pattern is straightforward: apps/ holds your individual applications; packages/ holds shared code — utilities, UI components, and config files like tsconfig.

You could technically replicate this directory structure without Turborepo, but Turborepo adds real value by reading each package.json, running builds and tests in bulk or in parallel, and handling build cache intelligently — a significant advantage for larger projects where build times start to hurt.


Why a Monorepo in the AI Era?

Knowledge base management is increasingly recognized as critical in AI-era product development — and that knowledge base naturally includes your codebase. A knowledge base only has value if it's actually referenced. And to be referenced easily, knowledge needs to be close together.

With a monorepo, the moment you clone a single GitHub repository, you have everything in one place: application code, development documentation, agent skills — the full knowledge base for your product. You could devise ways to help AI recognize multiple repositories as belonging to the same project, but why bother? A monorepo gives you that coherence for free.

A common objection goes something like: "Frontend and backend only need to communicate via API contracts — mixing their contexts is unnecessary." I'd argue this mental model is worth updating. In the age of AI coding, the leverage comes from architectures that let you see the full picture and move fast — not from strict context separation.


Conditions for Monorepo Success

Monorepos pair well with AI-era development, but a few conditions make them work significantly better.

1. Your backend is written in TypeScript

A mixed stack — TypeScript frontend, Python backend — can technically be a monorepo, but the shared packages/ become frontend-only, narrowing their value. You also end up managing linting, formatting, and tooling for two different languages.

Worth noting: Turborepo is built around TypeScript (and package.json as the primary unit of configuration), so aligning your stack on TypeScript is the path of least resistance here too.

2. Your review process is adapted for AI-assisted development

In a monorepo with AI coding, implementing a single feature often means the AI writes frontend and backend code in one go. Pull requests naturally reflect that end-to-end implementation. You could split them up — but if you're still enforcing pre-AI-era review rituals, humans become the bottleneck. At minimum, integrating AI-assisted code review is necessary; a broader rethink of your PR workflow is worth considering.


A Real Example: Chronock

My own app, Chronock, is built as a monorepo with TypeScript across the full stack. Here's the structure:

chronock/
├── apps/
│   ├── chronock-app/              # Frontend (React + TanStack Start)
│   ├── chronock-backend/          # Backend (Bun + Connect RPC)
│   ├── chronock-book/             # Booking page (React + TanStack Start)
│   └── chronock-www/              # Marketing site (Astro)
│
├── packages/
│   ├── contract/                  # Proto definitions & generated code
│   ├── i18n/                      # Internationalization utilities
│   └── typescript-config/         # Shared TypeScript config
│
├── specs/                         # Product specifications
│
├── .claude/
│   ├── CLAUDE.md
│   └── skills/
├── .codex/
│   └── skills/
├── biome.jsonc                    # Linter / Formatter config
├── bunfig.toml                    # Bun config
├── docker-compose.local.yml
├── package.json
└── turbo.json                     # Turborepo config
Enter fullscreen mode Exit fullscreen mode

A few things worth highlighting:

  • The marketing site (Astro) lives in the monorepo alongside the app. This makes tasks like "reflect new feature messaging on the website" or "check for inconsistencies between the latest implementation and the landing page copy" natural things to hand off to an AI agent.

  • Frontend–backend communication uses Connect RPC (Protocol Buffers). The .proto definition files live in packages/contract, making it seamless to manage the interface definition and run codegen for both frontend and backend from one place.

  • Product specs and agent skills live in the same repository, which makes coding agents noticeably more effective — they have the context they need without jumping across repos.


Summary

Architecture choices for the AI era:

  • Centralize context in a monorepo and treat it as a living knowledge base
  • Aligning your stack on TypeScript pays off in multiple ways
  • The pull request problem doesn't solve itself — it needs its own dedicated solution

That's a wrap for Part 1.

Top comments (0)