Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Enterprise UI, v2

Enterprise UI, v2

Avatar for Steve Kinney

Steve Kinney

June 15, 2026

More Decks by Steve Kinney

Other Decks in Education

Transcript

  1. • Microfrontends • App Shell & Islands Architecture • Server

    Components & Streaming • Monorepos • Backends for Frontends Day 1: Architecture Patterns In This Course Day 2: Operating & Evolving • Dependency & Release Management • Scaling TypeScript • Setting Up Guardrails • Design Systems • Deployment Pipelines • Testing (Playwright + MSW + HARs) • Observability • Migration Patterns
  2. • One codebase, one build, one deploy—the simplest possible architecture.

    • Routing, state, styles, tests, dependencies all tightly coupled. • It’s the basic building block and almost always the right place to start. • The monolith is the default for a reason: low operational overhead, simple mental model. The Good Parts™ Monoliths
  3. • A frontend monolith is one application built as a

    single unit. • A microfrontend system is many independently deliverable frontend slices composed into one user experience. Runtime Architecture The Three Axes
  4. • A monorepo is one repository holding many applications and

    libraries. • A polyrepo gives each project its own repository. Polyrepos as the model where each team can make their own organizational decisions, at the cost of harder code sharing and repeated maintenance across repositories. Repository Topology The Three Axes
  5. • You can have one deployable, many deployables that still

    require lockstep coordination, or many deployables that are genuinely independent. Fowler warns against build-time “microfrontends” packaged into one container bundle—you get package boundaries, but you reintroduce lockstep release. Deployment Topology Three Axes
  6. • A monorepo can host a classic monolith. • It

    can host several unrelated apps. • It can host runtime-composed microfrontends. • A microfrontend architecture can live in one repository, many repositories, or some hybrid in between. Monorepos and microfrontends are not opposites. The Big Correction
  7. • Team Collisions: Multiple teams editing the same fi les,

    stepping on each other’s work, blocked by merge con fl icts. • Build Times: A one-line change rebuilds and retests everything. CI takes 45 minutes. Deploys become events. • Blast Radius: A bug in Settings takes down Authentication. Every change is a risk to the whole product. • Dependency Hell: Upgrading React means upgrading everything at once. So nobody upgrades anything. The Hard Parts™ Monoliths
  8. • An architectural style where a frontend is decomposed into

    smaller, independently deliverable applications composed together. • Independent Deployment: each piece ships on its own schedule. • Team Ownership: a team owns a vertical slice from UI to data end-to-end. • Technology Agnostic: each microfrontend can use di ff erent frameworks—in practice, they don’t and probably shouldn’t. Decomposition at the application boundary. What Is a Microfrontend?
  9. You have multiple teams that: • need to ship at

    di ff erent cadences • need clearer ownership, or • need to migrate a large frontend incrementally instead of stopping the world for a rewrite. When it might be the right choice. What is a Microfrontend?
  10. A strong HTML- fi rst posture, works well with SSR

    and progressive enhancement. It’s not trendy in the age of SPAs, but it works. Server-side composition
  11. This is where you have a container application that pulls

    them all in as dependencies. • That looks neat on paper because you get one bundle and dependency deduplication. • But, every change forces a rebuild and release of the whole container. All of the pain and less of the gains. Build-time integration
  12. • It’s simple to implement, at least. • Advantages: Strong

    isolation of styling and globals. • Disadvantages: Basically, everything else. Routing, history, deep-linking, responsiveness, and cross-app integration all become harder There when you need it. But, only if you need it. Runtime integration via iframes
  13. • Use import(‘./module.js’) to load up individually deployed applications at

    run-time. • When it’s done right, it should feel like a monolith application with lazy- loading. Load up the di ff erent parts dynamically at run time. Runtime integration via JavaScript modules
  14. • Basically, it’s the same as the previous approach, but

    we’re going Web Components as the delivery mechanism. • Good luck with server-side rendering. A variation on a theme. Runtime integration via Web Components
  15. They also force decisions about routing, state, styling, design systems,

    and communication contracts. They’re never just a loading strategy.
  16. Applications are loaded and composed in the browser at runtime.

    Runtime Composition Host Application Remote A (Analytics) Remote B (Users) Remote C (Settings)
  17. • The dominant implementation of runtime composition • A host

    application declares remote entry points • Remotes expose speci fi c modules at known URLs • Webpack/Rspack resolves and loads modules at runtime • Shared dependencies (React, etc.) are negotiated to avoid duplication • Key vocabulary: Host (shell), Remote (exposes modules), Shared (negotiated dependencies), Exposes (public API) How it works under the hood. Module Federation
  18. • Independent deployments per team • Autonomous release cycles •

    Incremental upgrades (migrate one remote at a time) • Team-scoped blast radius • Technology fl exibility (within reason) What You Get The Trade-O ff s What It Costs • Shared dependency versioning complexity • Runtime failures (network, version skew) • Harder to maintain UX consistency • Increased infrastructure overhead • Debugging spans multiple codebases
  19. • Shared Dependencies: How do you ensure two remotes don’t

    load di ff erent versions of React? Who decides the “blessed” version? • Routing and Navigation: Who owns the URL? How do you transition between remotes without a full page reload? • Version Skew: Remote A deployed Tuesday. Remote B deployed Friday. Their shared contract changed Thursday. Now what? • Failure Isolation: Remote C fails to load. Does the whole app crash? Do you show a fallback? Cascading failures? Architectural decisions that matter more than which bundler you pick. The Hard Problems
  20. Tiny reactive stores (< 1KB) shared across frameworks. Perfect for

    cross-remote state: user context, feature fl ags, theme. Framework-agnostic, tiny, reactive. But, yet another shared dependency to version. Nanostores
  21. Browser API for sending messages between browsing contexts. Works across

    tabs and iframes. Zero dependencies, built into the browser. But string- based, no type safety. BroadcastChannel
  22. Makes Web Workers feel like local async functions. Useful when

    remotes need to o ffl oad work or communicate. Great DX, hides postMessage complexity. Primarily for Worker communication. Comlink
  23. Laboratory Experiment 1. Wire up a host shell and a

    remote analytics module with Module Federation. 2. Con fi gure shared dependencies (React) and resolve version negotiation. 3. Add cross-boundary communication: the shell holds auth context, the remote needs it. 4. Introduce BroadcastChannel or nanostores to bridge the gap. Runtime Composition
  24. • Composed at build time as npm packages • Single

    versioned artifact deployed together • Simpler infrastructure, no runtime loading • Type-checked across boundaries at compile time • One build, one deploy, guaranteed consistency Build-Time Composition Build-Time vs. Runtime Runtime Composition • Loaded dynamically in the browser • Independent deployments per remote • Shared dependency negotiation at runtime • Network-dependent: version skew, failures • More operational complexity, more autonomy
  25. • Each microfrontend is a package: @app/analytics, @app/users, @app/ settings.

    • Packages export React components, hooks, and utilities. • The host app imports them as regular dependencies. • TypeScript project references enforce boundaries at compile time. • Shared code lives in @app/shared—one version, no negotiation. The practical middle ground between monolith and runtime microfrontends. Build-Time Composition in Practice
  26. Laboratory Experiment 1. Set up a monorepo with @app/shell, @app/analytics,

    and @app/shared packages. 2. Wire up build-time imports: the shell imports analytics as a package dependency. 3. Add TypeScript project references for cross-package type checking. 4. Compare the developer experience with runtime Module Federation. Build-Time Composition
  27. • Composition at the component level instead of the application

    level. • Static HTML surrounds interactive islands. • Each island hydrates independently—on its own schedule. • The surrounding HTML ships zero JavaScript. • Astro: the most widely adopted islands framework. • The architectural insight: most of your page doesn’t need to be interactive. Ship zero JavaScript by default. Hydrate only what needs to be interactive. Islands Architecture
  28. • Ship HTML fi rst. • Hydrate only real interactive

    regions. • Optionally defer server fragments as server islands. The Short Version TL;DR
  29. • The page is mostly static or server- rendered content

    • Important interactions are localized, not page-wide • You care about initial load and HTML- fi rst behavior • You want explicit control over when client code wakes up • Marketing sites, content sites, e- commerce, docs Choosing the Island Life Use When Avoid When • Your product is a dense, stateful application • Most regions are live and deeply coordinated • You need deep provider trees and app-wide context • The isolation that makes islands elegant becomes friction • You’ve had a bad day at work.
  30. • SSR = source of HTML. • Islands = what

    hydrates after HTML. • SSR pages can still fully hydrate. • Streaming SSR changes HTML delivery timing. • Islands change which parts hydrate. • They can be used together. Subtle Di ff erences SSR vs Islands
  31. • Run only on the server—zero JavaScript sent to client.

    • Direct access to databases, fi le systems, APIs. • Can’t use useState, useEffect, or event handlers. • Render to a serializable format streamed to client. • Ideal for: data fetching, layout, static content. Server Components The Server/Client Boundary Client Components • Run in the browser (optionally SSR’d) • Full access to browser APIs and interactivity • useState, useEffect, event handlers all work • JavaScript bundle is shipped to the client. • Ideal for: forms, interactions, real-time UI.
  32. • Don’t wait for everything—send what you have, stream the

    rest. • renderToPipeableStream: React’s streaming API. • Shell and fast content render immediately. • Suspense boundaries de fi ne where the stream pauses until data resolves. • HTML is injected in-place as data becomes available. • The UX impact: users see content progressively instead of waiting for the slowest API. Progressive rendering instead of all-or-nothing. Streaming SSR
  33. • Suspense boundaries aren’t just loading states—they’re architectural decisions •

    They determine what streams when and what shows a fallback • Header + Nav renders immediately (no Suspense needed) • Main Content streams when its data resolves • Sidebar (slower API) streams later, independently • Placement of Suspense boundaries is a design decision with real UX consequences Deciding what streams when. Suspense as Architecture
  34. Laboratory Experiment 1. Build a data-heavy analytics view with multiple

    API calls that resolve at di ff erent speeds 2. Use renderToPipeableStream to stream the shell immediately 3. Add Suspense boundaries—experiment with di ff erent placements 4. See the UX impact: what streams fi rst? What shows a fallback? Server Components & Streaming
  35. • Code Ownership: Teams own packages, not repos. Same boundaries

    without the operational overhead of independent deploys. • Shared Code: One version of everything. No version negotiation, no duplicate dependencies, no “which React are we on?” • Atomic Changes: Refactor across package boundaries in a single PR. Update a shared type and fi x every consumer in one commit. • Type Safety: TypeScript checks cross boundaries at compile time. No runtime contract violations. No version skew. Maybe you just need better code organization. The Case for Monorepos
  36. • Symlinked packages—local dependencies resolve like npm packages • Shared

    node_modules via content- addressable store. • Workspace protocol: "@app/ui": "workspace:*" • Filtering: pnpm --filter @app/web run build • Dependency hoisting control. What You Get pnpm Workspaces What’s Missing • No task orchestration (what runs in what order?). • No caching (every build is a full build). • No a ff ected-package detection. • No dependency graph visualization. • No remote caching or CI optimization.
  37. • Task Graph: Builds a DAG of your tasks. Knows

    @app/web depends on @app/ui, so builds ui fi rst. Parallelizes everything it safely can. • A ff ected Detection: Changed @app/ui? Only rebuild packages that depend on it. Everything else is a cache hit. CI time drops dramatically. • Caching: Content-hash based. If inputs haven’t changed, skip the work and replay cached output. Local by default, remote for CI. • turbo.json: Declarative pipeline con fi g. De fi ne task dependencies, outputs to cache, and env inputs. One fi le, entire monorepo orchestration. Task orchestration and caching for monorepos. Turborepo
  38. • Legitimate alternative to Turborepo. • More opinionated—generators, linting, project

    structure. • Stronger dependency graph visualization. • Built-in support for many frameworks. • Better for teams that want guardrails and conventions. Nx Honorable Mentions
  39. • Google-scale build system—you’ll encounter it, not adopt it. •

    Hermetic builds—every input explicitly declared. • Language-agnostic (Go, Java, JavaScript, everything) • Extreme caching and parallelism at massive scale. • Steep learning curve, heavy con fi g, overkill for most. Bazel Honorable Mentions
  40. Laboratory Experiment 1. Take the build-time composition work and organize

    it into a monorepo with pnpm workspaces. 2. Get packages linked and see the dependency graph resolve. 3. Layer Turborepo on top—con fi gure the pipeline in turbo.json. 4. Run turbo build and watch caching skip unchanged packages 5. Change one package and see a ff ected detection in action. Monorepos
  41. A client-speci fi c backend layer: one backend per user

    experience, instead of one shared general-purpose backend trying to please every consumer at once. • Client-Speci fi c: Each frontend gets a tailored server-side facade shaped to its needs. • Team-Owned: Ideally owned by the same team as the frontend so API and UI evolve together. • Composition Layer: Orchestrates, aggregates, trims, and reshapes backend data for the client. Isn’t that like most backends? Backends for Frontends
  42. • One shared API trying to serve all clients. •

    Mobile makes 6+ calls to paint one screen. • Tight coupling to internal service boundaries. • Payload bloat—every client gets everything. • Release bottleneck from central API team. Without Backends for Frontends With • Each client gets a tailored facade. • One call out, one composed response back. • Backend topology hidden from the client. • Payload trimmed per client’s actual needs. • Frontend team owns its own release cadence.
  43. • Monolith: BFF as a translation layer between a legacy

    API and a modern frontend. Probably the most common real-world scenario. • Monorepo: BFF per app or shared BFF with per-client query surfaces. Ties into build-time composition naturally. • Microfrontends: BFF per team or domain boundary. Each team owns their frontend and their API layer end-to-end. BFFs across the architectural patterns. Backends for Frontends
  44. • Your team owns Pulse, a product with three clients:

    a web dashboard, a mobile app, and an internal CLI tool. • All consume the same set of backend services (Users, Analytics, Billing). • Each client needs di ff erent data shapes, auth fl ows, and response sizes. A problem in search of a solution. Consider This
  45. • Version Drift: Team A is on React 18.2, Team

    B on 18.3, Team C just upgraded to 19. Shared components work… di ff erently. • Diamond Dependencies: Package A depends on Lib v1. Package B depends on Lib v2. Your app depends on both. Which Lib wins? • Phantom Dependencies: Your code imports a package you never declared. It works because a sibling hoisted it. Until that sibling is removed. • Update Fatigue: 10 packages, each with independent dependencies. Dependabot opens 47 PRs. Nobody reviews them. Security debt grows. In a monolith, dependencies are invisible.
 In a distributed architecture, they’re your biggest risk. The Dependency Problem
  46. • Program size • Barrel fi les • Type complexity

    • Declaration complexity • Ambient type pollution • Circular dependencies Things to look for when things get slow. Common Culprits
  47. Failure mode Symptom Primary fi x Oversized program Slow builds,

    high memory Narrow include, set "types": [] Barrel fi les One import pulls hundreds of modules Direct imports, remove re-export index fi les Complex types Slow checking, quadratic unions Prefer interfaces, name conditional types Anonymous exports Huge .d.ts output Add explicit return types to exports Ambient type pollution Slow startup, duplicate globals Set "types": [], list only what’s needed Typed linting Slow ESLint, full project analysis Narrow tsconfig for linting, disable type rules selectively Module-resolution drift Works locally, breaks consumers Use nodenext, avoid extensionless imports Circular dependencies Deep instantiation errors, runtime undefined Break cycles, restructure package graph
  48. • TypeScript type-checks the entire codebase on every change •

    30-second IDE feedback loops • CI takes forever • Developers lose trust in the type system Without References Project References Without References • Each package compiles independently • TypeScript uses .d.ts boundaries between packages • Incremental builds only recheck what changed • IDE stays fast, builds stay fast
  49. • composite: true — enable project references in each package.

    • references: [...] — declare cross-package dependencies. • tsc --build — build only changed packages + dependents. • declaration: true — emit .d.ts fi les as the package boundary. • Path aliases for clean imports across package boundaries. • Result: the type system scales with your codebase instead of against it. The con fi guration that makes TypeScript monorepos actually scale. Key TypeScript Con fi guration
  50. Before fl ipping fl ags, fi gure out where the

    time is actually going. Measure First
  51. • High Files + high I/O Read time → problem

    is your file set or module resolution. • High Check time → problem is your types. • --generateTrace produces a Chrome-compatible trace. Look at checkSourceFile and checkExpression. Finding out why things went bad. Measure First
  52. • composite: true, declaration: true, incremental: true. • Imports resolve

    through declaration output, not full source. • declarationMap gives you cross-project “Go to De fi nition.” Project References
  53. Laboratory Experiment 1. Add composite: true and project references to

    the monorepo packages 2. Run tsc --build and compare build times against a fl at tsc 3. Change a type in @app/ui and see only downstream packages recheck 4. Set up path aliases so imports resolve cleanly across boundaries Scaling TypeScript
  54. • Import Boundaries: Prevent packages from importing across team boundaries.

    @app/users can’t reach into @app/billing internals. • Banned Dependencies: New packages must go through review. Prevent moment.js from sneaking back in. Enforce the blessed version. • No Relative Imports: Force imports through the package’s public API. ../../packages/shared/src/utils is now a lint error. • Custom Rules: Server components can’t import client-only hooks. Shared packages can’t depend on app-speci fi c code. ESLint isn’t just about semicolons. It’s architectural enforcement at scale. Beyond Code Style
  55. • Can you solve it with an existing rule or

    plugin? → Use that. • Is it a convention speci fi c to your codebase? → Write a custom rule. • Does it enforce an architectural boundary? → Definitely write a rule. When to do it and when to not do it Writing Custom Rules
  56. Laboratory Experiment 1. Con fi gure eslint-plugin-boundaries to enforce package

    import rules 2. Intentionally violate a boundary—see the lint error fi re. 3. Add a banned-dependency rule to prevent a package from being imported. 4. Write a simple custom rule that enforces a convention speci fi c to your project. Architectural Linting
  57. Building everything on every PR doesn’t scale. But building nothing

    is worse. The Monorepo CI Problem Detect Changes Build Affected Test Affected Deploy Changed Turborepo’s affected detection + remote caching makes monorepo CI fast. Without them, CI time scales linearly.
  58. • Work fl ow: Triggered by an event. Lives in

    .github/workflows/. • Job: Runs on a fresh runner. Passes state to other jobs explicitly via outputs and needs. • Step: Runs sequentially inside a job. Shares the fi lesystem with other steps in the same job. • State fl ows within a job via GITHUB_ENV and GITHUB_OUTPUT. State fl ows between jobs via needs.job_id.outputs. Work fl ows contain jobs. Jobs contain steps. Jobs run in isolation. The Mental Model
  59. • Matrix Strategy: Run each package’s tests in parallel. Matrix

    over the list of a ff ected packages. Scale horizontally, not sequentially. • Selective Deploys: Use path fi lters or --a ff ected output to trigger only relevant deployment work fl ows. No unnecessary deploys. • Remote Cache: Turborepo remote cache in CI. First developer to build saves time for every subsequent run. 80%+ cache hit rates are common. • Preview Environments: Spin up ephemeral environments per PR. Test the actual deployed artifact, not just the CI build. Kill on merge. Things to take advantage of: An incomplete list. GitHub Actions Patterns
  60. Not all triggers are created equal. Trigger Semantics Trigger Runs

    on Has secrets? Risk level push Pushed commit Yes Low pull_request Merge commit SHA Read-only Low pull_request_target Base branch Full High workflow_dispatch Manual trigger Yes Low
  61. • pull_request uses the merge commit, not the head commit.

    That’s why the SHA doesn’t match what you pushed. • pull_request_target runs with full secrets on the base branch—never checkout untrusted code here. Not all triggers are created equal. Trigger Semantics
  62. Three levels of reuse, each with di ff erent tradeo

    ff s. Reuse Mechanisms Mechanism Scope Secrets access De fi ned in Action Single step Caller’s action.yml Composite action Multi-step Caller’s action.yml Reusable work fl ow Full job(s) Caller’s + own .github/workflows/
  63. • Set permissions to read-all or narrower at the work

    fl ow level. Don’t rely on the defaults. • Pin third-party actions to full commit SHAs, not tags. Tags are mutable. • Never run pull_request_target with actions/checkout pointed at github.event.pull_request.head.ref. • Use OIDC for cloud auth instead of long-lived credential secrets. • Treat ${{ github.event.*.title }} and other user-controlled fi elds as injection vectors—always use intermediate environment variables. The defaults are permissive. Tighten them. Security Patterns
  64. • If you don’t set limits, entropy will fi ll

    the space. • Initial JS bundle: < 200 KB gzipped — enforced by bundler con fi g + CI check. • Route-level chunk: < 50 KB per route — enforced by bundler warnings. • Third-party JS: < 100 KB total — enforced by import cost plugin + CI. • LCP target: < 2.5 seconds — enforced by Lighthouse CI in the pipeline. Constraints that keep you honest. Performance Budgets as Constraints
  65. Laboratory Experiment 1. Set up a GitHub Actions work fl

    ow with Turborepo for the monorepo. 2. Con fi gure remote caching and verify cache hits on a second run. 3. Use a matrix strategy to parallelize per-package test suites. 4. Add Lighthouse CI to enforce the performance budget from this morning. Pipeline Dreams
  66. • Works with fetch, Axios, Apollo, React Query—anything that makes

    HTTP requests. • Browser: Service Worker intercepts outgoing requests. Requires mockServiceWorker.js in your public assets. • Node: Native module interception. No worker fi le, no poly fi lls. • Same handlers work in both environments. Write once, use in tests, Storybook, and local development. MSW intercepts at the network boundary, not at the call site. Network-Level Interception
  67. Base handlers de fi ne the happy path. Per-test overrides

    handle the exceptions. Runtime Overrides
  68. • server.use() prepends handlers for the duration of the test.

    • server.resetHandlers() in afterEach restores the base handlers. • Add { once: true } for one-shot overrides that auto-remove. • Avoid asserting that a request was made. Assert the user-visible behavior that results from it. Base handlers de fi ne the happy path. Per-test overrides handle the exceptions. Runtime Overrides
  69. End-to-end tests that exercise the real composed application. Catch integration

    failures that unit tests miss. Use for critical user fl ows, not exhaustive coverage. Playwright
  70. Mock Service Worker: intercept network requests at the service worker

    level. Test frontend behavior without backend dependencies. Realistic mocking for both tests and development. Mock Service Worker
  71. Record real API responses as HAR fi les. Replay in

    tests for deterministic, fast integration tests. Catch contract drift when the recording changes. HAR Replay
  72. • Chromatic — Storybook-based, captures every story as a baseline.

    • Playwright screenshots — capture pages at key states, di ff against baselines. • Percy — cross-browser visual snapshots in CI. Visual Regression Testing
  73. Laboratory Experiment 1. Write a Playwright test for a cross-remote

    user fl ow (navigate between microfrontend routes). 2. Use MSW to mock the BFF layer—test the UI without a running server. 3. Record a HAR from a real API call, replay it in a test, and verify deterministic results. 4. Discuss: where would contract testing catch a bug that these tests miss? Testing Strategies
  74. • Package Structure: Design tokens, primitives, and composed components in

    separate packages. Teams depend on the layer they need, not the whole system. • API Surface Control: Limit what’s exported. If it’s not in the public API, it can’t be used. Internal components stay internal via package boundaries. • Upgrade Paths: Codemods for breaking changes. Deprecation warnings in current version. Migration guides that aren’t afterthoughts. In which, you create your own shared dependency nightmare. Design System Governance
  75. Sentry, Datadog, or Bugsnag. Source maps per remote. Alerts per

    team. The key: error boundaries that report which remote failed, not just that something failed. Error Tracking
  76. Real User Monitoring tied to speci fi c remotes and

    routes. Web Vitals per microfrontend. Know which team’s code is slow, not just that the page is slow. Performance
  77. Trace from the browser through the BFF to backend services.

    OpenTelemetry spans. Essential for debugging cross-boundary issues. Distributed Tracing
  78. • You know all these architectures now. How do you

    get there from where you are? The strangler fi g grows around the existing tree, eventually replacing it • Stage 1: 100% Legacy — new system exists but serves no tra ffi c • Stage 2: First Routes — proxy or router sends some URLs to the new system • Stage 3: Feature Parity — most tra ffi c on the new system, legacy shrinking • Stage 4: Complete Migration — legacy is fully decomposed or retired Incremental migration without the big-bang rewrite. The Strangler Fig Pattern
  79. • Automated code transformations that make large-scale changes survivable •

    jscodeshift: Facebook’s AST-based transformation toolkit — understands code structure, not just text • ts-morph: TypeScript-aware AST manipulation — understands types, not just syntax • Custom transforms: migrate component APIs, rename imports, update deprecated patterns • All in a single PR — one atomic change across the entire monorepo • Find-and-replace is fragile; AST transforms are precise Automated code transformations that make large-scale changes survivable. Codemods
  80. Laboratory Experiment 1. Set up a routing-level strangler fi g:

    legacy and modern apps coexist behind a shared entry point 2. Migrate one route from the legacy app to the new architecture 3. Write a jscodeshift transform that migrates a deprecated component import across the monorepo 4. Run the codemod, verify the transform, and see the blast radius in a single pull request. Strangler Fig + Codemods
  81. • Document the why, not just the what—Future You™ will

    thank Present You™. • Title: short decision name (e.g., “Use build-time composition over Module Federation”) • Status: Proposed → Accepted → Deprecated → Superseded • Context: what situation prompted this decision? What constraints exist? • Decision: what are we going to do? Be speci fi c and actionable. • Consequences: what trade-o ff s are we accepting? What will this cost? ADRs: the documentation pattern that actually works. Architecture Decision Records