System Design Space
Knowledge graphSettings

Updated: April 1, 2026 at 9:00 AM

Frontend application architecture: app shell, feature modules, and shared kernel

easy

How to structure frontend as a modular system: app shell, feature modules, shared kernel, ownership boundaries, and evolution rules that keep the shared layer from spreading uncontrollably.

Frontend application architecture becomes important the moment the codebase no longer fits in one team’s head and every change starts touching routing, shared utilities, and neighboring features. That is where app shells, feature modules, and strict rules around what belongs in the shared kernel become necessary.

The practical value of the chapter is that it helps separate product flow from platform structure. Good frontend modularity speeds delivery not because the folders look cleaner, but because ownership, import boundaries, and public module APIs become predictable.

For design reviews, it is a strong entry point into modular-monolith frontend thinking: how to keep the shell thin, how to stop the shared layer from expanding, and how to evolve the application without jumping into micro-frontends too early.

Practical value of this chapter

Design in practice

Turn guidance on frontend application boundaries, modularity, and a controlled shared kernel into concrete decisions for composition, ownership, and client-runtime behavior.

Decision quality

Evaluate architecture through measurable outcomes: delivery speed, UI stability, observability, change cost, and operating risk.

Interview articulation

Structure answers as problem -> constraints -> architecture -> trade-offs -> migration path with explicit frontend reasoning.

Trade-off framing

Make trade-offs explicit around frontend application boundaries, modularity, and a controlled shared kernel: team scale, technical debt, performance budget, and long-term maintainability.

Context

Why do we need frontend architecture?

This chapter turns the high-level frontend architecture overview into the structure of a real application: shell, modules, and shared kernel.

Читать обзор

In a complex frontend product, application architecture defines not only code structure, but also how independently teams can move, how quickly screens load, and where the public contracts between interface areas actually live.

A useful rule is to design module boundaries before debating state libraries or framework patterns. Boundaries are where it becomes clear what is truly shared and what simply has not been decomposed into domains yet.

Architectural frame

The app shell owns the frame, not business logic

The shell should carry layout, navigation, identity, telemetry, and platform hooks. Business features should not turn it into a shared monolith.

Feature modules align with bounded contexts

Catalog, checkout, billing, editor, or reporting should have their own screens, data hooks, and internal UI primitives instead of constantly reaching into a global shared layer.

The shared kernel must stay small and expensive to change

Design tokens, routing primitives, auth session, telemetry adapters, and a few platform utilities belong there. Everything else starts local to a domain.

Public contracts matter more than convenient imports

Each module needs entrypoints, versionable APIs, and clear ownership. Otherwise the app turns into a tightly coupled graph of accidental dependencies.

Related

Decomposition Strategies

A frontend app scales better when module boundaries match product bounded contexts.

Open chapter

Boundary rules

Imports only flow downward through the architecture

A feature module may depend on the shared kernel, but not directly on a neighboring feature. Cross-domain scenarios should go through a public contract or orchestration layer.

UI components split into local and platform primitives

Buttons, modal shells, and token-aware typography belong to the platform. Domain widgets like a pricing table or document toolbar should not leak into shared too early.

Routing is architecture, not just DX

The route tree should reflect ownership and lazy-loading boundaries. Nested layouts and loaders should not accidentally pull the whole runtime of every module.

Critical cross-cutting concerns connect the same way everywhere

Telemetry, error boundaries, auth guards, and feature-flag evaluation need one integration pattern so debugging and rollout do not depend on local team style.

Team operating model

  • Each feature module has an owner team, a public API, and explicit deprecation rules.
  • The shared kernel goes through stricter review than local domain code because its blast radius is much higher.
  • Architectural dependency rules are captured in ADRs or import-policy checks, not in informal agreements.
  • Refactoring starts with clearer module boundaries, not with an early jump to micro-frontends.

Practical quality bar

Independent change

A new feature can be added inside one domain without a cascade of edits across neighboring modules and shared utilities.

Transparent dependencies

From the import graph and route tree, it is obvious where the source of truth lives and who owns each public entrypoint.

Controlled shared kernel

The common layer stays small, versionable, and rarely changed instead of becoming a dumping ground for everything reusable.

Related chapters

Enable tracking in Settings