TypeScript Project Structure: A Scalable Folder and Configuration Guide
project structuretsconfigmonoreposarchitecturecode organizationdeveloper workflow

TypeScript Project Structure: A Scalable Folder and Configuration Guide

TTypeScript Toolbox Editorial Team
2026-11-04
7 min read

A practical TypeScript project structure checklist for Node.js, React, Next.js, and monorepos, including tsconfig, tests, aliases, and builds.

A good TypeScript project structure makes code easier to find, test, build, and change. This guide provides a reusable checklist for organizing Node.js, React, Next.js, and monorepo applications, with practical guidance for tsconfig inheritance, shared types, path aliases, generated code, build outputs, and review points that help a codebase remain maintainable as it grows.

Overview

There is no single correct TypeScript folder structure. A small command-line tool does not need the same boundaries as a multi-package product, and a Next.js application has framework conventions that should not be replaced without a clear reason. The useful goal is not maximum abstraction. It is predictable organization: developers should know where application code, reusable modules, tests, configuration, and generated files belong.

Start with a few durable boundaries:

  • Source code: Put production code in a clearly named directory such as src, unless a framework strongly favors another convention.
  • Tests: Keep tests beside the code they exercise or in a dedicated test directory, but apply one convention consistently.
  • Configuration: Keep compiler, lint, test, and bundler configuration at the repository or package root.
  • Build output: Write compiled files to a directory such as dist or build, and keep it out of source control.
  • Generated code: Separate generated API clients, database types, or schemas from hand-written code and document how to regenerate them.

These boundaries also make migration easier. If you are converting an existing JavaScript application, establish the folders and build process incrementally rather than moving every file at once. The JavaScript to TypeScript migration checklist can help you sequence that work.

Checklist by scenario

Small Node.js application or library

For a focused service, script, or package, avoid creating folders before you need them. A practical starting point is:

project/
├── src/
│   ├── index.ts
│   ├── config.ts
│   └── features/
├── test/
├── package.json
├── tsconfig.json
├── tsconfig.build.json
└── README.md

Keep the entry point thin. Put business logic in modules that can be imported and tested independently. If the application exposes HTTP routes, group route handlers, services, and data-access code by feature once those areas become substantial. Do not create an elaborate controller-service-repository hierarchy for a project that has only a few functions.

  • Check that src contains source files only.
  • Check that tests do not import from compiled output.
  • Check that the package entry points match the files actually produced by the build.
  • Check that development commands and production commands use intentionally different settings when necessary.

React application

React projects often benefit from a feature-oriented structure once the component tree becomes difficult to navigate:

src/
├── app/
│   ├── App.tsx
│   └── providers.tsx
├── features/
│   ├── billing/
│   │   ├── components/
│   │   ├── api.ts
│   │   ├── types.ts
│   │   └── billing.test.ts
│   └── account/
├── components/
├── hooks/
├── lib/
├── styles/
└── main.tsx

Use features for code that belongs to one product area and shared folders for genuinely reusable code. A component used by one feature should usually stay inside that feature. Move it to a shared directory only when its API and responsibilities are clear.

Keep browser-only code separate from server or build tooling code. Avoid placing all TypeScript types in a global types folder by default; local types are easier to maintain when they live near the functions and components that use them.

Next.js application

Next.js projects should respect the routing convention selected by the application. With the App Router, a structure might look like:

app/
├── layout.tsx
├── page.tsx
├── dashboard/
│   ├── page.tsx
│   └── loading.tsx
└── api/
    └── health/
        └── route.ts
src/
├── components/
├── lib/
├── server/
└── types/
public/
next.config.ts
 tsconfig.json

Keep route-specific files close to their routes, while placing reusable UI and data-access modules in src or another clearly documented location. Be explicit about server-only modules. A shared utility that imports server credentials should not be accidentally consumed by a client component.

For a broader reference, see this guide to TypeScript with Next.js project structure and type-safe data fetching.

Monorepo

A monorepo should make package ownership and dependency direction visible:

repo/
├── apps/
│   ├── web/
│   └── api/
├── packages/
│   ├── config/
│   ├── domain/
│   ├── ui/
│   └── validation/
├── tooling/
├── package.json
├── tsconfig.base.json
└── workspace configuration

Use apps for deployable applications and packages for independently consumed libraries. Shared packages should have narrow responsibilities and explicit public entry points. Avoid a catch-all package such as common that becomes a dumping ground for unrelated helpers.

Define dependency rules before the repository becomes large. For example, UI packages may consume domain types, while a domain package should not import application-specific UI code. Project references or package-level builds can reinforce those boundaries, but the exact setup should match the chosen build tools.

What to double-check

TypeScript configuration

Use a base configuration for shared compiler settings and package-specific configurations for build targets. A common pattern is:

// tsconfig.base.json
{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "isolatedModules": true
  }
}

// tsconfig.json
{
  "extends": "./tsconfig.base.json",
  "compilerOptions": {
    "noEmit": true
  },
  "include": ["src", "test"]
}

Review include, exclude, rootDir, outDir, module settings, and declaration output rather than copying a configuration without understanding it. The TypeScript tsconfig guide provides a useful reference for these choices.

Path aliases

Aliases can improve imports, but they must work in every environment that runs the code: the editor, TypeScript, tests, the bundler, and production. A paths entry in tsconfig does not automatically teach Node.js how to resolve an alias at runtime. Verify the complete toolchain before adopting aliases widely. Use the TypeScript path alias guide when debugging this boundary.

Runtime data and generated files

TypeScript types disappear at runtime. Validate data crossing an API, environment, file, or database boundary instead of assuming that a declared interface makes external data safe. Keep schemas near the boundary or in a dedicated validation package, and decide whether generated types are committed, generated during CI, or produced as part of a package build. The guides to runtime validation with Zod cover practical patterns for this decision.

Build and test boundaries

Decide whether tsc is responsible for type checking only, emitting JavaScript, or both. Bundlers and alternative build tools may handle transformation differently. Whatever the choice, keep a separate, repeatable type-check command and ensure tests exercise source code or built code intentionally rather than accidentally mixing both.

Common mistakes

  • One global types folder: This often becomes a collection of unrelated declarations. Keep types close to their domain unless they are truly shared.
  • Deep import paths: Importing internal files from another package couples consumers to implementation details. Expose stable package entry points instead.
  • Aliases used as architecture: A short import does not create a dependency boundary. Document which layers may depend on one another.
  • Mixing generated and hand-written code: Regeneration can overwrite manual changes or create confusing review noise. Separate the directories and mark generated files clearly.
  • Ignoring build output: Accidentally publishing source maps, test files, or development-only modules can make packages harder to consume. Inspect the package contents before release.
  • Overusing barrel files: Broad index.ts exports can create circular dependencies and increase import coupling. Add public barrels deliberately.
  • Copying framework conventions blindly: A structure that suits Next.js may not suit a Node.js library. Start from runtime and deployment requirements.

When to revisit

Review the project structure before a seasonal planning cycle, a major framework or build-tool change, or a migration milestone. Revisit it when a folder has become a catch-all, when developers cannot identify ownership, when tests require unusual setup, or when a package must be reused outside its original application.

Use this short review checklist:

  1. Can a new contributor find the application entry point, test command, and build output quickly?
  2. Are feature boundaries clearer than they were during the last review?
  3. Do aliases, package exports, and generated files work in the editor, tests, builds, and production?
  4. Are runtime validation and environment configuration located at the correct boundaries?
  5. Do monorepo packages expose only the APIs that consumers should rely on?
  6. Can you remove a folder or configuration option without breaking an undocumented workflow?

Make structural changes in small, reviewable steps. Move one boundary, update imports and tests, run the type checker and build, then document the convention. A scalable TypeScript project structure is not a fixed diagram; it is a set of clear decisions that remains understandable as the application, toolchain, and team change.

Related Topics

#project structure#tsconfig#monorepos#architecture#code organization#developer workflow
T

TypeScript Toolbox Editorial Team

TypeScript Editors

Senior editor and content strategist. Writing about technology, design, and the future of digital media. Follow along for deep dives into the industry's moving parts.