Skip to content

Frontend

the half of a system that runs in the reader's browser

What it is

A frontend is the part of a software system that people look at and operate: the pages, screens, forms and controls, along with the code that draws them and responds to input. It is also written front end or front-end, and on the web the same territory is often called the client side.

On the web, a frontend is assembled from three languages a browser understands. HTML gives a page its structure and meaning, CSS decides how it looks, and JavaScript adds behaviour. The browser is what the W3C’s Architecture of the World Wide Web calls a user agent, software acting on behalf of a person, which retrieves data from servers and presents it. Everything a frontend sends to it ends up on a device that belongs to someone else.

Its partner is the backend, which stores data, applies the business rules and answers requests. The two talk over HTTP, usually through an API. It is tempting to draw the line between them where the network is, but current frameworks blur it. In Next.js, pages and layouts are Server Components by default: they produce what the user sees and execute on a server. A "use client" directive at the top of a file marks the boundary, and that file, its imports and the components it renders join the bundle sent to the browser. What makes code frontend is its job, presentation and interaction, and where it executes is a separate decision.

Why it matters

The first decision is where the work happens. The W3C architecture document describes a spectrum with the client doing everything at one end and the server doing everything at the other, and observes that most designs combine the two. Where an application belongs depends, in the document’s terms, on the power of the client, the power and load of the server, and the bandwidth between them. It names the trade-off from both sides: tailoring content on the server lowers caching efficiency, and when the number of possible clients is unbounded, pushing computation out to them scales better. The usual positions are pages built once, pages rendered on a server per request, pages assembled in the browser, and mixtures of these. A page that is the same for everyone is cheapest built once and cached. A page showing one customer’s data is rendered per request somewhere, and choosing the browser means every reader downloads the code that does it.

The second decision is how far to trust it, and the working answer is that the server trusts none of it. Everything a frontend delivers can be read, edited and replayed by the person holding the browser. A hidden button or a form that refuses to submit is a courtesy to an honest user and a minor detour for anyone else, so every permission and validation that matters is repeated on the server. Secrets follow the same rule: an API key that reaches the bundle is public, whatever the variable is called. Content Security Policy narrows what an injected script can do, and its specification presents it as defence in depth, never a substitute for validating input and encoding output carefully.

The frontend is also where accessibility is won or lost. WCAG 2.2, the W3C Recommendation published in December 2024, sets testable success criteria, and many of them turn on frontend choices: whether a control can be reached and operated from a keyboard and whether it exposes a name and a role to assistive technology. Native HTML elements meet several of them out of the box, and a custom widget has to rebuild each one by hand.

Finally, a frontend that knows how the database stores things has to change whenever the database does, while one that keeps to presentation lets each side move at its own pace.

What it looks like in practice

Where a frontend renders is decided part by part on one platform, whose Next.js 16 frontend chooses a rendering mode for each part of the app. Marketing and public pages are generated statically, and the interactive parts are rendered in the browser. On the authenticated dashboard and the large directory listings, where the N+1 pattern turned one page into a cascade of round trips, a factory called createPrefetchedServerPage fetches a page’s data on the server and hands it to the client already filled in, and a cache-invalidation strategy is documented for each data request. A page moved onto the factory loads in one round trip, and the next one to move inherits that behaviour. The migration is still in progress, with the rest of the authenticated app rendered on the client and queued behind it.

A layered maritime platform gives its frontend one job and nothing else. The Next.js frontend handles presentation and interactivity. A deliberately thin Go API, built with Huma over Fiber, routes and validates requests, applies security filtering and orchestrates, while the business logic lives in PostgreSQL functions. When a rule changes, it changes in one layer, in SQL, and the frontend carries on unaffected when the schema shifts underneath it. The boundaries were written down and then enforced in code review, and that separation is what let the product keep adding features while the architecture stayed sound.

The work of hardening an application starts from the assumption that the client is hostile. On the frontend, the Next.js proxy middleware in proxy.ts sets a Content Security Policy with a nonce generated per request and strict-dynamic, along with HSTS and SameSite cookies. The boundary itself sits on the server. Entitlements, covering tier, role, organization and ship scoping, are checked again on the server for every request, and the frontend’s gates count as user experience only: they shape what a person sees, and the server settles what that person may do.

Where it goes wrong

Frontend failures tend to share a cause: something was decided in the browser that belonged on the server, or the other way round.

An API key shows up in the JavaScript any visitor can download. In Next.js, an environment variable reaches the browser bundle only when its name starts with NEXT_PUBLIC_, and any other variable read from client code is replaced with an empty string. A request that comes back unauthorised, followed by a quick rename to make it work, inlines the secret into a public file. Keep code that needs a secret on the server, and mark its module with the server-only package so that importing it from a Client Component fails the build. Rotate any key that has already shipped.

import "server-only";
export async function getInvoices(customerId: string) {
    const url = `${process.env.BILLING_URL}/customers/${customerId}/invoices`;
    const headers = { Authorization: `Bearer ${process.env.BILLING_KEY}` };
    const res = await fetch(url, { headers });
    return res.json();
}

A user reaches records the interface had hidden from them. The permission check lived in the frontend, as a route guard, a hidden menu entry or a disabled button. Anyone who reads the network traffic can call the API directly with other parameters. Check every entitlement on the server for every request, against the signed-in user and the specific record, and treat the frontend’s checks as a convenience for honest users.

The page fills in one spinner at a time. Each component fetches its own data after it renders, so a child’s request waits for its parent’s and the page arrives as a waterfall of round trips. Fetch a page’s data together and close to its source, on the server where the framework allows, and pass it down.

Keyboard and screen-reader users get stuck on a custom control. A clickable div gets the mouse behaviour of a button and none of the rest: it takes no focus, ignores Enter and Space, and tells assistive technology nothing about what it is. WCAG 2.2 asks at level A that a keyboard can operate all functionality (success criterion 2.1.1) and that every component expose its name and role (4.1.2). Use button, a, input and label for what they are, and try each new screen with the keyboard alone before it ships.

Further reading

Glossary