Fresh Web Framework

repository·main·Indexed 12 days ago

https://github.com/denoland/fresh

A high-performance web framework for the Deno runtime featuring an island-based architecture for client-side interactivity and zero JavaScript by default. It provides built-in TypeScript support, file-system routing, and Ahead-of-Time (AOT) builds for optimized deployment to Deno Deploy. Includes official plugins for Tailwind CSS and Vite.

Tokens
91.9K
Snippets
334
Records
412
Agent score
95%

What's inside Fresh

  1. Overview of Fresh features

    main

    Fresh is a next-generation web framework built for speed, reliability, and simplicity. Key features include:

    • Island-based client hydration: Provides interactivity only where needed.
    • Zero runtime overhead: No JavaScript is shipped to the client by default.
    • No configuration necessary: Works out of the box.
    • TypeScript support: Built-in support for TypeScript.
    • File-system routing: Routing is handled via the file system, similar to Next.js.
  2. What is Fresh?

    main

    Fresh is a full-stack modern web framework for JavaScript and TypeScript developers. It is designed to build high-quality, performant, and personalized web applications such as blogs, e-commerce shops, or large-scale web applications.

    Core characteristics include:

    • Server-side Rendering: A combination of a routing framework and templating engine that renders pages on demand on the server.
    • Island Architecture: Server-rendered pages can contain specific areas made interactive on the client via optional hydration.
    • Preact-based: Uses Preact as the JSX rendering engine.
    • Zero Config: Requires no initial configuration to get started.
    • Minimal Client JS: The framework itself requires no client-side JavaScript, making it tiny and fast.
    • File-system Routing: Uses a routing system similar to Next.js.
    • TypeScript Support: Provides TypeScript support out of the box.
  3. Advanced Fresh concepts

    main

    For more complex application structures, Fresh provides:

    • App wrapper: Manages the outer HTML structure, typically handling everything up to the <body> tag.
    • Layouts: Allows you to re-use a shared UI structure when calling ctx.render() across different routes.
    • Partials: Enables streaming in server-generated content onto the current page.
  4. Core concepts in Fresh

    main

    Fresh is built around several fundamental abstractions:

    • Middleware: Functions that receive a request and return a Response. They can set headers or pass state. If a middleware returns a response without calling the next one, it acts as a handler.
    • Context: An object passed through every middleware used to share state, trigger redirects, or render HTML.
    • Routing: The mechanism that matches a URL to a specific chain of middlewares. Fresh uses File Routing (convention-based routing from the filesystem).
    • Data Fetching: The process of loading data on the server to pass it to page components.
    • Islands: Preact components that are rendered on the server but become interactive on the client.
    • Signals: A reactive state management system used within Islands.
    • Static Files: The mechanism for serving assets like images and CSS.
  5. What is middleware in Fresh

    main

    A middleware is a function that intercepts a request and returns a response. It receives a Context object containing the Request.

    Middlewares are typically used for:

    • Setting HTTP Headers
    • Measuring response times
    • Fetching data and passing it to subsequent middlewares or handlers via ctx.state
    • Access control and analytics

    Middlewares can be chained, allowing you to perform actions both before and after the next handler in the chain by awaiting ctx.next().

    const app = new App<{ greeting: string }>()
      .use((ctx) => {
        // Middleware to pass data via ctx.state
        ctx.state.greeting = "Hello world";
        return ctx.next();
      })
      .use(async (ctx) => {
        // Middleware to modify the response after the next handler finishes
        const res = await ctx.next();
        res.headers.set("server", "fresh server");
        return res;
      })
      .get("/", (ctx) => {
        // A handler (a form of middleware that responds)
        return ctx.render(<h1>{ctx.state.greeting}</h1>);
      });
  6. What is an app wrapper and when to use it

    main

    The app wrapper is the outermost component in Fresh's rendering hierarchy. It is rendered only on the server and defines the shared <html, <head>, and <body> tags for every page in your application.

    Use an app wrapper to:

    • Set the document language (e.g., <html lang="en">).
    • Include global <meta> tags, fonts, or stylesheets.
    • Add analytics scripts or structured data to every page.
    • Set a global <body> class or data attribute.
    • Provide a consistent HTML skeleton without repeating it in every layout.
  7. Access shared state from Middleware in components

    main

    Middleware can attach data to ctx.state, which is then accessible to all downstream handlers and page components. This is useful for sharing information like user sessions across your application.

    // routes/_middleware.ts
    import { define } from "@/utils.ts";
    
    export default define.middleware(async (ctx) => {
      const session = await getSession(ctx.req);
      ctx.state.user = session?.user ?? null;
      return ctx.next();
    });
    
    // routes/dashboard.tsx
    import { page } from "fresh";
    import { define } from "@/utils.ts";
    
    export const handler = define.handlers({
      GET(ctx) {
        if (!ctx.state.user) {
          return ctx.redirect("/login");
        }
        return page();
      },
    });
    
    export default define.page((ctx) => {
      return <h1>Welcome, {ctx.state.user.name}</h1>;
    });
  8. Core Concepts of Fresh: Island Architecture

    main
    Fresh is built on the Island Architecture. The framework renders server-generated HTML pages by default and only ships JavaScript for specific parts of the page that require interactivity (known as 'islands'). This results in a lightweight footprint and high performance because the client receives minimal JavaScript.
  9. Use Route Groups to organize layouts

    main

    Route groups allow you to group related routes under a shared layout without affecting the URL structure. To create a route group, wrap a folder name in parentheses, such as (marketing) or (info).

    Inside a group folder, a _layout.tsx file will apply to all routes within that group. This is useful when different sets of routes need different layouts (e.g., a marketing layout vs. an admin layout) even if they share similar URL segments.

    Warning: Avoid creating routes in different groups that map to the same URL (e.g., (group-1)/about.tsx and (group-2)/about.tsx), as this creates ambiguity in route matching.

    └── <root>/routes
        ├── (marketing)
        │   ├── _layout.tsx  # applies to about.tsx and career.tsx
        │   ├── about.tsx
        │   └── career.tsx
        └── (info)
            ├── _layout.tsx  # applies to archive.tsx and contact.tsx
            ├── archive.tsx
            └── contact.tsx
  10. Understand the Fresh render hierarchy

    main

    Fresh renders components in a nested hierarchy. The app wrapper is the outermost layer, followed by layouts, and finally the specific page component:

    1. App wrapper (_app.tsx): Outermost layer; provides <html>, <head>, and <body>.
    2. Layouts (_layout.tsx): Shared page chrome like navigation, sidebars, or footers.
    3. Page component: The specific route content.

    In this hierarchy, the app wrapper wraps the layouts, and the layouts wrap the page.

  11. Register multiple nested layouts

    main

    You can register multiple layouts for different paths. Layouts are inherited from parent paths based on specificity. A layout registered at "*" applies to all routes, and more specific layouts are layered on top of it.

    For example, if you have a layout for "*" and a layout for "/admin/*", a request to /admin/dashboard will be wrapped by both: MainLayout will be the outer wrapper, and AdminLayout will be the inner wrapper.

    const app = new App()
      .layout("*", MainLayout) // Applied to all routes
      .layout("/admin/*", AdminLayout) // Added on top for /admin/* routes
      .get("/", (ctx) => ctx.render(<h1>Home</h1>))
      .get("/admin/dashboard", (ctx) => ctx.render(<h1>Dashboard</h1>));