Sitemap

Creating a Frontend Reference Architecture for Customer-Facing Implementations

6 min read6 days ago

--

Create a maintained implementation baseline for routing, domain modules, data access, customer configuration, accessibility, and production operations.

Press enter or click to view image in full size

A frontend reference architecture gives teams a maintained baseline for building customer-facing applications. It explains how the application starts, how features communicate, how data reaches the interface, and how the product is tested and operated. Its purpose is to make recurring implementation decisions clear enough that teams can deliver consistent results.

Customer implementations introduce different branding, identity providers, integrations, and deployment constraints. Without explicit extension points, those differences can spread through shared code and create expensive forks. A useful architecture defines the common foundation and the places where variation is expected.

Define the scope of the frontend reference architecture

Begin with supported product types and delivery constraints. A public content site, an authenticated customer portal, and an embedded administration panel can require different rendering, caching, and navigation choices. Document which scenarios the baseline supports and which require a separate decision.

Identify the outputs implementers need: a working starter application, a repository layout, dependency rules, interface contracts, representative tests, and deployment guidance. Include architecture decision records for choices with significant tradeoffs. Keep those decisions connected to the running example so documentation and code can be reviewed together.

  • Product constraints: critical journeys, accessibility needs, supported browsers, and localization requirements.
  • Integration constraints: identity providers, API contracts, tenant boundaries, and external services.
  • Operational constraints: hosting, release ownership, rollback, monitoring, and support expectations.
  • Extension requirements: branding, configurable capabilities, and customer-specific workflows.

Define architecture layers and allowed dependencies

Use boundaries that reflect ownership and change. The application shell coordinates startup, navigation, session transitions, localization setup, and global error handling. Domain modules implement cohesive product capabilities. Integration adapters translate external services into contracts those modules can consume.

Application shell
- startup, route composition, session lifecycle
- locale setup, top-level error and loading states
Domain modules
- billing, orders, account management
- public entry points and feature-owned behavior
Integration adapters
- HTTP transport, response validation, error mapping
- implementations of domain-facing service contracts
Shared foundations
- design system, formatting, telemetry interfaces
- reusable utilities with explicit ownership

The shell can compose modules, while modules depend on public contracts and shared foundations. Avoid importing another module’s internal store or implementation files. Where features need to collaborate, define a public API or coordinate through an application service. Review the dependency graph for cycles as the system grows.

Folder names do not enforce boundaries. Use dependency checks and package entry points to detect unsupported imports. TypeScript path aliases are a convenience for resolution; they do not create architectural isolation. The TypeScript module reference also explains that the paths option does not rewrite emitted imports, so build and runtime resolution must agree with the compiler configuration.

Publish a repository structure with clear ownership

A starter should demonstrate the intended boundaries through one complete user journey. Keep the layout understandable before adding code generation. The following structure is illustrative and can be adapted to a monorepo or a single application.

src/
app/
bootstrap/
routing/
composition/
modules/
billing/
public.ts
contracts/
ui/
model/
adapters/
billing-http/
shared/
ui/
formatting/
telemetry/
configuration/
schema/
loading/
tests/
integration/
journeys/

Define who maintains each area and what belongs in a public entry point. Keep shared utilities focused; an unowned shared directory can become a route around every boundary. Promote code into shared foundations when there is a demonstrated reuse need and a maintainer prepared to support its consumers.

Separate API transport from domain behavior

Components should receive a stable view of the data they need. Put endpoint paths, transport headers, response validation, and backend error translation in adapters. This lets a domain module evolve its interface independently from a particular service’s response shape and makes alternative implementations easier to test.

Example: a domain-facing data contract

export interface InvoiceSummary {
id: string;
number: string;
amountMinor: number;
currency: string;
}
export interface InvoicePage {
items: InvoiceSummary[];
nextCursor: string | null;
}
export interface InvoiceReader {
list(input: {
cursor?: string;
signal?: AbortSignal;
}): Promise<InvoicePage>;
}

This contract defines a shape, not a complete implementation. An adapter must validate untrusted response data at runtime, map failures, and respect cancellation. Define currency units and formatting separately so amounts remain consistent across products. TypeScript types alone do not validate a network response.

Get Subrahmanyam Poluru’s stories in your inbox

Join Medium for free to get updates from this writer.

Distinguish remote server state from local interface state. Track cache identity, refresh behavior, mutation invalidation, and error recovery explicitly. A customer or account switch should clear or partition relevant cached data and prevent in-flight results from being displayed in the next context.

Model customer variation through validated configuration

Treat branding, locale availability, navigation choices, and enabled capabilities as explicit configuration where practical. Validate configuration against a versioned schema, define defaults, and reject unsupported combinations before users encounter broken pages. Keep browser-visible configuration free of secrets.

Tenant configuration can guide presentation, but the server must authorize access to resources independently. Hiding a navigation item or disabling a button does not enforce a permission. The OWASP authorization guidance recommends validating permissions on every request. Test cross-tenant access attempts at the service boundary as well as normal frontend navigation.

Reserve custom adapters or modules for behavior that configuration cannot express clearly. Document their contract and compatibility expectations. Repeated customer-specific branches inside shared components are a signal to review the extension model.

Choose rendering and routing based on the experience

Record where HTML is generated, which content is cached, and when client code becomes interactive. Public pages may need indexable content and meaningful metadata; authenticated workflows need appropriate cache isolation and predictable session handling. Evaluate the actual routes before choosing one rendering strategy for the entire product.

Document direct-link behavior, browser Back and Forward navigation, missing routes, and expired sessions. For server rendering, isolate request-specific state and keep personalized output out of shared caches. For client-side transitions, define title updates, focus behavior, loading feedback, and recovery from failed code or data requests.

Build accessibility and localization into the baseline

Include accessible navigation, form patterns, dialogs, error summaries, and asynchronous feedback in the starter. Define which responsibilities belong to the shell and which remain with feature teams. A design system can supply maintained primitives, while application integration determines whether the complete journey is usable.

Establish translation loading, locale-aware formatting, and text direction behavior. Verify long labels, narrow viewports, zoom, keyboard interaction, and supported assistive technologies. Treat loading, empty, error, and success states as part of every example rather than leaving them for consumers to invent.

Define observability and recovery contracts

Provide a shared way to report failures with bounded route identifiers, release versions, and safe correlation metadata. Exclude credentials, form values, and sensitive query parameters from logs. Apply consent, retention, and access rules to telemetry collection.

Distinguish rendering failures, rejected API operations, and transport problems so support teams can investigate the right boundary. Error boundaries can contain some rendering failures, but asynchronous operations still need explicit handling. Show a recovery action that fits the task and avoid automatic retries for consequential mutations without a server-supported duplicate-prevention strategy.

Make architecture verification part of CI

Automate checks that can provide dependable feedback: supported dependency directions, type compatibility, configuration validation, and representative integration behavior. Add journey tests for sign-in, session expiry, core tasks, and customer switching where supported. Keep fixtures realistic enough to exercise empty and failure states.

  • Contracts: validate adapter mappings, pagination, cancellation, and error handling.
  • User journeys: verify direct navigation, recovery, and meaningful state transitions.
  • Accessibility: combine automated checks with manual keyboard and assistive technology review.
  • Performance: track route delivery size and representative loading and interaction budgets.
  • Deployment: verify environment configuration, asset availability, and supported rollback paths.

Keep frontend checks connected to service-side tests. A successful UI test does not prove authorization, tenant isolation, or backend transaction integrity. Assign verification to the boundary that can actually establish the requirement.

Document deployment and upgrade paths

Explain artifact creation, environment configuration, static asset caching, and the relationship between frontend and API versions. Retain assets needed by active sessions during a rollout where the deployment model requires it. Test what happens when a user keeps an old page open while a newer release is deployed.

A generated starter does not automatically inherit future fixes. Publish migration notes, supported package versions, and an upgrade process for existing implementations. Assign ownership for propagating shared improvements and tracking known exceptions.

A reference architecture earns adoption when its maintained examples make reliable delivery easier.

Measure adoption through delivery outcomes

Pilot the architecture with a real implementation and use feedback to simplify confusing boundaries. Track time to a first working journey, recurring integration defects, upgrade effort, and unresolved exceptions. Template usage is useful context, but does not establish that teams are benefiting from the architecture.

Give exceptions an owner, a reason, and a review point. When several teams need the same exception, revisit the baseline. Review the reference implementation alongside documentation so its recommendations remain credible as frameworks, services, and product requirements evolve.

--

--

0 followers

Specialist in Design Systems | Forward Deployed Engineer | Polyglot Frontend | FastAPI | Node.js | RAG | MCP | LangChain Lear more about me at polurus.com

See more from me