Vorma Documentation

repository·main·Indexed 19 days ago

https://github.com/vormadev/vorma

A full-stack framework combining a Go backend with React, Solid, or Preact frontends using Vite. Vorma provides a developer experience similar to Next.js or Remix, featuring nested routing, end-to-end type safety, parallel-executed route loaders, and Hot Module Replacement (HMR). The framework includes a client-side runtime for managing programmatic navigation, data mutations via submit, and state revalidation.

Tokens
31.3K
Snippets
114
Records
140
Agent score
66%

What's inside Vorma

  1. What is Wave Build Tool?

    main

    Wave is a Go-based build system for web applications. It manages static asset processing, CSS compilation (via esbuild), file watching, hot reloading, and optional Vite integration for JavaScript/TypeScript.

    Key characteristics:

    • No external tooling required: Orchestrated entirely from within your Go repository and dependencies.
    • Unified Pipeline: Provides both development (with browser refresh and CSS hot reloading) and production (with asset hashing, minification, and critical CSS inlining) modes.
    • Versatile: Can be used as a full frontend build tool or as a simple file watcher that restarts a server upon file changes.
  2. What is Vorma?

    main

    Vorma is a full-stack framework for Go that provides a developer experience similar to Next.js, Remix, or TanStack Start. It uses Go for the backend and supports React, Solid, or Preact for the frontend.

    Key features include:

    • Nested routing
    • End-to-end type safety (extending to Link components)
    • Parallel-executed route loaders
    • Vite integration for full Hot Module Replacement (HMR) during development.
  3. Standard Vorma Project Structure

    main

    A standard Vorma project, as generated by the Vorma bootstrapper CLI, is organized into backend and frontend directories. The backend handles Go-based server logic, routing, and the Wave build system, while the frontend manages TypeScript/React assets, styles, and type-safe API interactions. The entire structure is configurable via a wave.config.json file.

    your-vorma-app/
    ├── backend/
    │   ├── assets/
    │   │   └── entry.go.html
    │   ├── cmd/
    │   │   ├── build/
    │   │   │   └── main.go
    │   │   └── serve/
    │   │       └── main.go
    │   ├── dist/
    │   │   ├── static/
    │   │   │   └── .keep
    │   │   └── main(.exe) (compiled Go binary)
    │   ├── src/
    │   │   └── router/
    │   │       └── router.go
    │   ├── wave.config.json
    │   ├── wave.dev.go
    │   └── wave.prod.go
    ├── frontend/
    │   ├── assets/
    │   ├── src/
    │   │   ├── components/
    │   │   ├── styles/
    │   │   │   ├── main.css
    │   │   │   └── main.critical.css
    │   │   ├── vorma.api.ts
    │   │   ├── vorma.entry.tsx
    │   │   ├── vorma.gen.ts
    │   │   ├── vorma.routes.ts
    │   │   └── vorma.utils.tsx
    ├── .gitignore
    ├── go.mod
    ├── package.json
    ├── tsconfig.json
    └── vite.config.ts
  4. What is a Task in Vorma?

    main

    A Task is a fundamental primitive in Vorma. It is a function that takes an input, returns data (or an error), and is guaranteed to run a maximum of one time per unique input value within a specific execution context (e.g., a web request lifecycle).

    Key characteristics include:

    • Automatic Memoization: Results are cached per input within the context to prevent redundant work.
    • Deduplication: If multiple tasks depend on the same sub-task, that sub-task runs only once.
    • Type-Safety: Uses Go generics to ensure compile-time safety for inputs and outputs.
    • Concurrency Support: Designed to be thread-safe and supports parallel execution via ctx.RunParallel.
    import "github.com/vormadev/vorma/kit/tasks"
    
    // A Task is essentially a wrapper around a function with this signature:
    // func(ctx *tasks.Ctx, input T) (R, error)
  5. How Vorma handles build ID mismatches

    main

    Vorma manages deployment mismatches (skew) through two primary mechanisms:

    1. Automatic Safe Hard Reloads: During standard route navigation or route data revalidation, if Vorma detects an outdated build ID, it automatically performs a hard reload. This ensures the client gets the latest HTML, entry module, and CSS without disrupting the user experience.
    2. Event Dispatching: During API queries or mutations, where a hard reload might be disruptive, Vorma dispatches a build ID change event instead. This allows developers to handle the update gracefully via addBuildIDListener.

    When combined with Vercel Skew Protection, API requests from outdated clients are routed to the correct deployment via the x-deployment-id header, preventing errors during the transition period.

  6. Use generated TypeScript for type safety

    main

    Vorma automatically generates a TypeScript file (conventionally at ./frontend/src/vorma.gen.ts) to provide end-to-end type safety between your Go backend and frontend.

    This file includes:

    • Loader and action route definitions (inputs, outputs, patterns, and param keys).
    • Ad hoc types shared from the backend.
    • Extra TypeScript code generated from the Go-based builder (constants, enums).
    • App-specific type helpers for links and other features.
    • A vormaViteConfig object for use in your vite.config.ts.

    You can customize the output path of this file using the Vorma.TSGenOutPath field in your Wave configuration.

  7. Configure the Wave build system

    main

    Wave is the lower-level build tool used by Vorma. It provides hot reloading without requiring external tool installation.

    • Configuration: Use backend/wave.config.json to customize the names and locations of project items. This file can be renamed or moved, provided you point to it when instantiating the Wave instance in Go.
    • Development: backend/wave.dev.go handles Wave instantiation for development (using the !prod Go build tag).
    • Production: backend/wave.prod.go handles Wave instantiation for production (using the prod Go build tag). In production, static assets are typically embedded into the compiled Go binary for performance.
  8. Understand Vorma URL segment types

    main

    Vorma categorizes URL segments into four types which determine how routes are matched and how data is loaded:

    1. Static Segments: A static string of URL-safe characters (e.g., "posts" in the pattern "/posts").
      • Base Slash Route ("/"): A special static segment that acts as an outer layout for the entire app. Data loaded here is referred to as RootData and is useful for providing global user data, feature flags, or environment variables.
    2. Dynamic Segments: Segments that start with a configurable dynamic params prefix (default is ':'). These represent variable parts of a URL (e.g., ":post_id").
    3. Index Segments: Represents the default child of a parent route. It is identified by an explicit marker (default is "_index"). To render a landing page for a route like "/posts", you would use the pattern "/posts/_index".
    4. Splat Segments: (Mentioned as a type, though specific implementation details are not provided in this section).
  9. How location access differs between React/Preact and Solid

    main

    Vorma provides reactive location data across different UI libraries. While the APIs are identical for React and Preact via a hook, Solid uses a direct accessor to leverage its fine-grained reactivity.

    • React/Preact: Use the useLocation() hook from vorma/react or vorma/preact.
    • Solid: Use the location accessor from vorma/solid. Note that location is a function that must be called to access properties (e.g., location().pathname).
    // React (or Preact)
    import { useLocation } from "vorma/react";
    
    function Component() {
    	const location = useLocation();
    	return <div>Current path: {location.pathname}</div>;
    }
    // Solid
    import { location } from "vorma/solid";
    
    function Component() {
    	return <div>Current path: {location().pathname}</div>;
    }
  10. Understand the default Vorma project structure

    main

    Vorma is highly flexible and has no required file conventions, as most settings are configurable via Wave. However, the default structure produced by the bootstrapper is as follows:

    Frontend (/frontend)

    • src/vorma.api.ts: Type-safe API client wrapper.
    • src/vorma.entry.tsx: Client entry point.
    • src/vorma.gen.ts: The generated TypeScript file containing types and helpers.
    • src/vorma.routes.ts: Registry of route components.
    • src/vorma.utils.tsx: Type-safe hooks and utilities.
    • assets/: Client-exposed assets.

    Backend (/backend)

    • src/router/: Contains actions.go (queries/mutations), loaders.go (UI route data loaders), and core.go (HTTP router setup).
    • cmd/build/main.go: The Vorma build script.
    • cmd/serve/main.go: The actual HTTP server.
    • assets/: Server-only assets (e.g., templates like entry.go.html).
    • wave.config.json: Static configuration for the Wave build tool.
    • vorma.config.go: Dynamic Vorma/Wave configuration.
  11. How nested data loaders work in Vorma

    main

    Vorma uses nested data loaders tied to URL segments to preload data for routes in parallel. This process occurs in a single request, ensuring that response headers are merged predictably and preventing data drift or inconsistency across different route segments.

    Key benefits of this model include:

    • Single-request model: All data for the matched route segments is fetched in one go.
    • Coalescing/Memoization: Because loaders run within a single request, you can use the vorma/kit/tasks primitive to ensure expensive or repetitive operations (like user authentication checks) only run once per request, even if multiple route segments require them.
  12. Compose Tasks

    main

    Tasks can be composed by having one task call others within its implementation. Because of Vorma's automatic deduplication, if multiple tasks in a parallel set share a common dependency, that dependency will only be executed once per context. This allows for powerful, modular logic where you can inject checks (like authentication or subscription status) into many tasks without worrying about performance overhead.

    // Task composition example
    var EnrichedUserTask = tasks.NewTask(func(ctx *tasks.Ctx, userID int) (*EnrichedUser, error) {
    	isSubscribed, err := FetchUserSubscriptionStatus.Run(ctx, userID)
    	if err != nil || !isSubscribed {
    		return nil, errors.New("subscription error")
    	}
    
    	var user *User
    	var orders *Orders
    
    	if err := ctx.RunParallel(
    		FetchUserTask.Bind(userID, &user),
    		FetchOrdersTask.Bind(userID, &orders),
    	); err != nil {
    		return nil, err
    	}
    
    	return &EnrichedUser{
    		User:   user,
    		Orders: orders,
    	}, nil
    })