Skip to content
Starone

Capability

Frontend architecture for fast, accessible and maintainable web applications

The browser is the least controlled runtime in any system: unknown devices, unreliable networks and untrusted input. We engineer the frontend as an architecture problem, deciding where code renders, where state lives and how the interface degrades, rather than as a styling exercise.

Overview

Frontend engineering is the design of the part of a system that executes on hardware and networks nobody on the team controls. A mid-range Android phone on a congested mobile connection parses JavaScript several times more slowly than a developer laptop, so every architectural choice has a direct cost in user-perceived latency: how much code ships, where HTML is produced, when the page becomes interactive and how many round trips stand between a click and a result.

The same choices determine how far the codebase and the team can scale. A frontend without module boundaries, a typed API contract or a shared component library becomes a single deployable in which every change risks a regression somewhere else, and delivery slows as engineers are added. Explicit boundaries between features, a design system with versioned tokens and components, and generated API types let several teams ship in parallel without coordinating every release.

We work in TypeScript with React and Next.js or Vue and Nuxt, and treat rendering strategy, state ownership, performance budgets, accessibility and browser security as first-order design decisions made per route and per feature. Each is recorded, measured in the pipeline and observed in production with real user data.

What we engineer

Technical areas

01

Component architecture and design systems

Components are split into presentational primitives, composed patterns and feature modules that own their data access, with dependencies allowed in one direction only. Design tokens for colour, spacing, typography and motion are the single source of truth, exported to CSS custom properties so that themes and brands change without touching component code. Lint rules on import paths enforce the boundaries so they survive team growth.

  • Design tokens and CSS custom properties
  • Headless, accessible component primitives
  • Storybook as component workbench and documentation
  • Feature-sliced module boundaries
  • TypeScript in strict mode
  • Visual regression testing
02

State management and API integration

Server state is a cache of data owned by the backend and needs fetching, deduplication, invalidation and retry; client state is ephemeral interface state such as open panels, drafts and selections. Treating both as one global store is the most common source of stale data and unnecessary re-renders. We keep server state in a query cache, shareable state in the URL, and reserve client stores for what remains.

  • TanStack Query and SWR for server state
  • Zustand, Redux Toolkit and Pinia for client state
  • URL search parameters as state
  • OpenAPI and GraphQL type generation
  • Optimistic updates with rollback
  • Request cancellation and deduplication
03

Rendering strategies

Server-side rendering, static generation, client-side rendering and streaming each trade time to first byte, time to interactive, cacheability and infrastructure cost differently. The choice is made per route according to how personalised and how fresh the content must be. Hydration cost is treated as a budget, reduced with server components, islands and deferred hydration.

  • Server-side rendering (SSR)
  • Static generation and incremental regeneration
  • Streaming SSR with Suspense boundaries
  • React Server Components
  • Single-page application shells
  • Selective and deferred hydration
  • CDN and edge caching
04

Performance and Core Web Vitals

Core Web Vitals measure loading (Largest Contentful Paint), responsiveness (Interaction to Next Paint) and visual stability (Cumulative Layout Shift) at the 75th percentile of real users. The published good thresholds are 2.5 seconds, 200 milliseconds and 0.1 respectively. We set budgets for bundle size and long tasks, enforce them in CI and verify them with field data, because laboratory scores do not represent slow devices.

  • Route-level and component-level code splitting
  • Responsive images in AVIF and WebP
  • Font subsetting and font-display control
  • Priority hints and resource preloading
  • Long-task breakup and main-thread scheduling
  • Lighthouse CI budgets
  • Real user monitoring
05

Accessibility and responsive architecture

Accessibility is built into the component library, where it is cheap, instead of being audited into finished pages, where it is expensive. We target WCAG 2.2 level AA: semantic HTML first, ARIA only where native semantics run out, full keyboard operability and managed focus in dialogs and route changes. Layout responds to container size and input type, not to a fixed list of device widths.

  • Semantic HTML and landmark structure
  • Keyboard navigation and focus management
  • Screen reader testing with NVDA and VoiceOver
  • Automated checks with axe-core
  • Container queries and fluid type scales
  • Reduced-motion and colour-contrast support
06

Security, PWAs and real-time interfaces

Everything in the browser is visible to the user and reachable by any script that executes on the page, so the frontend is designed to hold as little authority as possible. Service workers add offline caching, installability and background sync where the product needs them. Live data arrives over Server-Sent Events when the flow is one-way and over WebSockets when the client must also send at low latency.

  • Nonce-based Content Security Policy
  • Output encoding and Trusted Types against XSS
  • HttpOnly, Secure, SameSite session cookies
  • Subresource Integrity and dependency auditing
  • Service workers and Workbox caching strategies
  • WebSockets and Server-Sent Events

Choosing a rendering strategy per route

Rendering is not an application-wide setting. A marketing page, a product listing, an authenticated dashboard and a document editor in the same product have different freshness, personalisation and interactivity requirements, and frameworks such as Next.js and Nuxt let each route choose. The decision turns on two questions: can the HTML be shared between users, and how stale may it be.

Hydration is the hidden cost of server rendering. The server sends HTML, then the client downloads and executes the same component tree to attach event handlers, and until that completes the page looks ready but does not respond. Server components remove non-interactive components from the client bundle altogether, and streaming sends the shell immediately while slower data resolves behind Suspense boundaries, so one slow query no longer delays the whole response.

  • Static generation: content identical for all users and changing on publish, such as documentation and marketing pages; served from a CDN with the lowest latency and cost
  • Incremental regeneration: large catalogues where rebuild time is prohibitive and staleness of seconds to minutes is acceptable
  • SSR: personalised or request-dependent pages that need indexable HTML and a fast first paint, at the cost of server compute per request
  • Streaming SSR: pages combining a fast shell with slow data sources
  • SPA: authenticated, highly interactive tools where search indexing is irrelevant and the session is long, so a heavier first load is amortised

Architecture

Frontend rendering strategies

Build time (SSG)

  1. 01

    Content and data

    CMS, Markdown or API read at build

  2. 02

    Static generation

    Pages rendered once to HTML

  3. 03

    CDN

    Cached and served from the edge

  4. 04

    Revalidate

    Regenerate on change or on a timer

Request time (SSR)

  1. 01

    Request

    Per-user or fresh data is needed

  2. 02

    Server render

    Fetch data, render components

  3. 03

    Streaming

    Send the shell, then slower parts

  4. 04

    Hydration

    Attach handlers in the browser

Client (SPA)

  1. 01

    Client state

    UI state and optimistic updates

  2. 02

    API calls

    Typed requests to the backend

  3. 03

    Cache

    Dedupe, revalidate, invalidate

  4. 04

    Navigation

    Client routing with prefetch

Where a page is rendered decides its speed, freshness and cost. Most products mix all three: static generation for stable content, server rendering for personalised or fresh data, and client state for interaction.

Server state and client state are different problems

Most of what an application displays is not its own state. It is a snapshot of backend data that can become stale at any moment, may be requested by several components at once and must be refetched after a mutation. A query cache models this directly: each resource has a key, a staleness window, a retry policy and an invalidation rule, and components subscribe to the key instead of copying the data into a store.

Once server state leaves the global store, what remains is usually small: form drafts, wizard progress, interface preferences. Filters, sort order, pagination and selected tabs belong in the URL, which makes views linkable, restores them on reload and keeps the back button correct. A dedicated client store is justified for state that is shared across distant components and changes at high frequency, such as a canvas editor or a trading ticket.

How frontend architecture limits or enables team scale

A frontend scales with the team only if changes stay local. That requires feature modules with a public interface, a rule that features do not import each other's internals, and a shared layer of design-system components and API clients that is versioned and owned. In a monorepo, tools such as Turborepo or Nx use the dependency graph to build and test only what a change affects, which keeps pipeline time roughly flat as the codebase grows.

Micro-frontends push this further by giving each team an independently deployed slice, composed through module federation or at the edge. They solve an organisational problem, independent release cadence across many teams, and they cost duplicated dependencies, harder consistency and a more complex runtime. For most products a modular monolith frontend with enforced boundaries gives the same isolation with one build and one deployment.

The API contract is the other scaling constraint. Types generated from an OpenAPI document or a GraphQL schema turn a breaking backend change into a compile error in the pipeline instead of a runtime failure in production, and they remove a class of defensive code from every component.

How we engineer it

Our approach

We start from the constraints of the people using the product and the teams maintaining it, then fix the decisions that are expensive to reverse before any screen is built.

  1. 01

    Establish constraints and budgets

    We identify target devices, network conditions, indexing requirements, accessibility obligations and browser support, then convert them into numeric budgets for JavaScript per route, Core Web Vitals and interaction latency.

  2. 02

    Decide rendering and data ownership per route

    Each route is assigned a rendering strategy and cache policy, and each piece of state an owner: server cache, URL, component or client store. The session model and token handling are settled with the backend at this point.

  3. 03

    Build the design system first

    Tokens and accessible primitives are implemented and documented in Storybook before feature work, with keyboard behaviour, focus handling and contrast verified once at component level so that every screen inherits them.

  4. 04

    Implement features against typed contracts

    Feature modules consume generated API types and mock servers, which lets frontend and backend proceed in parallel. Unit tests cover logic, component tests cover behaviour and Playwright covers the critical journeys end to end.

  5. 05

    Enforce quality in the pipeline

    Every pull request runs type checking, linting, accessibility checks, bundle-size comparison and Lighthouse budgets against a preview deployment. A change that breaches a budget fails the build instead of being discovered after release.

  6. 06

    Measure in production and iterate

    Real user monitoring reports Core Web Vitals, JavaScript errors and interaction traces by route, device class and release. Regressions are traced to a specific deployment and fixed against field data.

Trade-offs

Decisions that shape the design

The questions we work through with your team before committing to an architecture.

Should this application be server-rendered or a single-page application?
Choose server rendering when pages must be indexed, shared as links with previews or painted quickly on slow devices, and accept the server compute and hydration cost. Choose an SPA for authenticated tools with long sessions and heavy interaction, where first-load weight is paid once and a static shell on a CDN is simpler to operate. Many products need both, split by route.
React with Next.js or Vue with Nuxt?
Both ecosystems cover SSR, static generation, streaming and typed data fetching, so the decision is mostly about the team. React has the larger hiring pool and library ecosystem and the more mature server component model; Vue offers a smaller API surface and fine-grained reactivity that needs less manual memoisation. Existing team skill and the existing codebase outweigh any technical difference between them.
Do we need a global state library at all?
Usually less than expected. Put server data in a query cache, view state in the URL and local state in components, then see what is left. Add Zustand, Redux Toolkit or Pinia when state is shared widely, updated frequently and needs devtools or time-travel debugging; Redux Toolkit's stricter structure pays off mainly in large teams.
Where should the browser keep authentication tokens?
Prefer a backend-for-frontend that holds tokens server-side and gives the browser an HttpOnly, Secure, SameSite cookie, with CSRF protection on state-changing requests. Tokens in localStorage are readable by any script that runs on the page, so one XSS flaw or compromised dependency exposes them. If tokens must live in the browser, keep them in memory, keep them short-lived and rotate refresh tokens.
Monolithic frontend, monorepo or micro-frontends?
Start with one application with enforced module boundaries. Move to a monorepo with shared packages when several applications share a design system and API clients. Adopt micro-frontends only when independent teams are blocked by a shared release train, because the runtime composition, version skew and duplicated payload are permanent costs.
WebSockets, Server-Sent Events or polling for live data?
Use Server-Sent Events for server-to-client streams such as notifications, progress and token-by-token AI output: they run over plain HTTP, reconnect automatically and pass through proxies easily. Use WebSockets when the client also sends frequent messages, as in collaboration or trading. Polling with conditional requests remains correct when updates are infrequent and a delay of several seconds is acceptable.

Production readiness

What makes it production-ready

How it is secured

A strict nonce-based Content Security Policy, framework output encoding and Trusted Types limit the impact of cross-site scripting, and session credentials stay in HttpOnly cookies out of reach of scripts. Dependencies are pinned by lockfile, audited in CI and loaded with Subresource Integrity when served from third-party origins. Authorisation is always enforced by the API; hiding a control in the interface is a usability measure, not a security control.

How it scales

Static assets are content-hashed, cached immutably and served from a CDN, so traffic growth mostly reaches the edge and not the origin. Server-rendered routes scale horizontally as stateless processes with cache-control and stale-while-revalidate tuned per route. Codebase scale is handled by module boundaries, affected-only builds and remote build caching.

How it is monitored

Real user monitoring records LCP, INP and CLS with attribution to the element or interaction responsible, segmented by route, device and release. Errors are captured with source maps uploaded privately, and browser spans are propagated to backend traces through OpenTelemetry so that a slow interaction can be followed into the API that caused it.

How it is deployed

Every pull request produces an isolated preview deployment on which automated and manual checks run. Releases are atomic and immutable, so rollback is a pointer change to the previous build, and feature flags separate deployment from release. Assets from the previous version remain available during rollout so that open sessions do not fail on missing chunks.

How it integrates

The frontend consumes REST or GraphQL APIs through generated clients, authenticates through OpenID Connect and receives live data over SSE or WebSockets. A backend-for-frontend aggregates calls and shapes payloads where the general-purpose API is too chatty. Analytics, consent, feature-flag and content-management services are integrated behind thin adapters so that a vendor can be replaced without touching feature code.

Technologies

Chosen to fit the workload and your existing estate.

  • TypeScript
  • React
  • Next.js
  • Vue
  • Nuxt
  • Vite
  • TanStack Query
  • Tailwind CSS
  • Storybook
  • Playwright
  • Vitest
  • axe-core
  • Workbox
  • OpenTelemetry

From zero to production

The engineering lifecycle

  1. 01

    Idea

  2. 02

    Discovery

  3. 03

    System architecture

  4. 04

    UX / product design

  5. 05

    Application engineering

  6. 06

    AI / data layer

  7. 07

    Cloud infrastructure

  8. 08

    Security

  9. 09

    QA & performance

  10. 10

    Deployment

  11. 11

    Observability

  12. 12

    Continuous evolution

FAQ

Common questions

How does frontend architecture affect user experience?

Directly. The rendering strategy sets how soon content appears, the amount of JavaScript sets how soon the page responds, and the way state and data fetching are organised sets whether interactions feel instant or wait on the network. Layout stability, focus behaviour and offline handling are also consequences of architecture, not of visual design.

What are Core Web Vitals and why do they matter?

They are three field metrics defined by Google: Largest Contentful Paint for loading, Interaction to Next Paint for responsiveness and Cumulative Layout Shift for visual stability, each assessed at the 75th percentile of real page views. They matter because they describe what users on real devices experience, and they are one of the signals used in search ranking.

Can an existing frontend be modernised without a rewrite?

In most cases, yes. An incremental migration places the old and new applications behind one router or reverse proxy and moves routes one at a time, sharing the session and design tokens between them. This keeps the product releasable throughout and lets each migrated route be measured against the one it replaced.

When is a progressive web application the right choice?

When the product benefits from installation, offline access or background sync but does not need deep device integration. A PWA shares one codebase with the website and avoids store review, but access to some device capabilities, background execution and push behaviour differs between platforms, particularly on iOS. Where those limits matter, a native or cross-platform mobile application is the better fit.

How is accessibility verified?

Automated tools such as axe-core catch a minority of issues, mainly missing names, roles and contrast failures, so they run in CI as a baseline. The remainder is verified by keyboard-only walkthroughs and screen reader testing with NVDA and VoiceOver on the critical journeys, against WCAG 2.2 level AA success criteria.

Have a system that needs to work in production?

Tell us what you are building. An engineer will read it and reply within two working days.