Skip to content

About

Single-page apps with your preferred server stack. Zero dependencies, vendorable, auditable (~300 lines). Server-side routing meets SPA UX.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Switchback

GitHub Release No Dependencies GitHub License CI

Build Single Page Applications around your server stack. Build in vanilla TypeScript, no framework lock-in, no supply-chain vulnerabilities.

Switchback is ~300 lines meant to be vendored. Audit the codebase before you finish your first cup of coffee, customize it, happy trails.

Examples

Here are some pairings you can spin up in a container to try out.

  • Zig - Backend owns form routing and state, frontend delivers app-like UX
  • Erlang - Server-side rendering with HTML morphing and stateful back-end
  • Go - Real-time visualization of a concurrent worker pool processing CPU-intensive tasks
  • Rust - Track bulk photos as they're swiftly processed in parallel
  • OCaml - Workflow states with compiler-enforced safety
  • C - Pure C99 multi-threaded server with optimistic updates
  • C# - ASP.NET minimal APIs with typed data contracts
  • Deno - Modern TypeScript runtime with type sharing between server and client
  • PHP - Persistent navigation with backend-controlled routing in vanilla PHP
  • Assembly - x86-64 assembly (because why not?)

Features

Your server owns routing and returns JSON for async requests, HTML for initial page loads. The client intercepts navigation and renders components.

  • Link interception (make <a> tags async)
  • Form interception (make forms async)
  • History management (back/forward buttons work)
  • Progress indicators (XHR option for download progress)
  • Scroll restoration (remembers scroll position)
  • Partial reloads (only refresh specific props)
  • Error handling (server errors, validation errors)
  • SSR support (morph server-rendered HTML into the DOM)

Installation

Pre-built bundles (no build step required):

Download ESM or UMD from releases

<script type="module">
  import { newSwitchback } from './switchback.esm.js';
</script>

TypeScript source:

Download source from releases and import directly

Vendor with git:

git submodule add https://github.com/ge3224/switchback vendor/switchback

Usage

Switchback coordinates between your server and client. The server detects async requests and returns JSON. The client intercepts links/forms and handles navigation.

Server Side

// Rust example using Axum web framework
// Check for X-Switchback header
let is_switchback = req.headers().get("X-Switchback").is_some();

// Build page response
let page = json!({
    "component": "Home",
    "props": {
        "stats": {
            "users": user_count
        }
    },
    "url": "/"
});

// Return JSON for Switchback, HTML otherwise
if is_switchback {
    Json(page).into_response()
} else {
    Html(render_html(&page)).into_response()
}

This pattern can be implemented easily in your favorite language. Check the demos for examples in Go, Rust, C, OCaml, Erlang, and more.

Client Side

Define your page components and initialize Switchback:

import { newSwitchback } from 'switchback';

// Simple DOM helper
function h(tag, props, ...children) {
  const el = document.createElement(tag);
  Object.assign(el, props);
  children.flat().forEach(child => {
    el.appendChild(
      typeof child === 'string'
        ? document.createTextNode(child)
        : child
    );
  });
  return el;
}

// Define page components
const pages = {
  'Home': (props) => h('div', {},
    h('h1', {}, 'Form Demo'),
    h('p', {}, `Submissions: ${props.stats.submissions}`)
  ),

  'Success': (props) => h('div', {},
    h('h1', {}, 'Success!'),
    h('p', {}, `Submitted: ${props.submitted.name}`)
  ),
};

// Initialize
newSwitchback({
  resolve: (name) => pages[name],

  setup: ({ el, App, props }) => {
    el.innerHTML = '';
    el.appendChild(App(props));
  },

  initialPage: window.initialPage,
});

Mark Elements for Async Navigation

Add data-swbk to links and forms:

<!-- Links navigate without page reload -->
<a href="/users" data-swbk>View Users</a>

<!-- Forms submit asynchronously -->
<form action="/" method="post" data-swbk>
  <input name="name" type="text" required>
  <input name="email" type="email" required>
  <button type="submit">Submit</button>
</form>

That's it. Your server returns JSON for X-Switchback requests, your client renders components, and Switchback handles the rest.

See the demos for complete working examples.

Inspiration

Switchback draws from Inertia.js philosophy while staying framework-independent and highly auditable.

License

MIT

About

Single-page apps with your preferred server stack. Zero dependencies, vendorable, auditable (~300 lines). Server-side routing meets SPA UX.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages