Fresh logo

Architecture

Fresh is a server-first web framework. Pages are rendered on the server and only the interactive parts (islands) ship JavaScript to the browser. This page explains how a request flows through the framework.

Request lifecycle

Request lifecycle flow diagram

Key concepts

Server-first rendering

Every page is fully rendered to HTML on the server before being sent to the browser. This means:

  • Pages are visible immediately - no blank loading screens
  • Search engines see complete content
  • Pages work without JavaScript enabled

Islands architecture

Fresh uses the islands architecture. Only components in the islands/ directory are hydrated in the browser. Everything else is static HTML that never runs JavaScript on the client.

This means a page with a single interactive button only ships the JavaScript for that button - not for the entire page.

Middleware chain

Middlewares execute in registration order, wrapping the handler. Each middleware calls ctx.next() to pass control to the next middleware (or handler). This creates an onion-like pattern where middlewares can act on both the request (before ctx.next()) and the response (after ctx.next()):

Typescript  
app.use(async (ctx) => {
  // Before: runs on the way in
  console.log("Request:", ctx.url.pathname);

  const response = await ctx.next();

  // After: runs on the way out
  console.log("Status:", response.status);
  return response;
});

Scoped middleware runs only for requests that match a specific path prefix. Pass a path pattern as the first argument to app.use():

Typescript  
app.use("/admin/*", async (ctx) => {
  // Only runs for /admin/* routes
  const user = ctx.state.user;
  if (!user?.isAdmin) return new Response("Forbidden", { status: 403 });
  return ctx.next();
});

Global middleware runs on every request; scoped middleware lets you apply logic like authentication or logging to a subset of routes.

Layout inheritance

Layouts wrap page components and are inherited from parent directories. A page at routes/blog/post.tsx inherits layouts from:

  1. routes/_layout.tsx (root layout)
  2. routes/blog/_layout.tsx (section layout)

Layouts nest from the outside in: the root layout is outermost, each deeper layout wraps closer to the page, and the innermost layout directly wraps the page component. The app wrapper (_app.tsx) wraps everything.

Build and deploy

Fresh uses Vite to bundle island JavaScript for production. The deno task build command:

  1. Discovers all islands and their dependencies
  2. Bundles client-side JavaScript with code splitting
  3. Generates a server entry point (_fresh/server.js)
  4. Hashes assets for cache busting

In production, _fresh/server.js serves the pre-built assets. In development, Vite provides Hot Module Replacement for instant feedback.