SwoffSwoff

No-Bundler

Comprehensive integration guide for Go, Laravel, Rails, Django, Flask, plain HTML/JS, and any no-bundler project.

No-Bundler

Swoff works with any project that doesn't use a JavaScript bundler. The generated files are static — drop them in your public directory and include a <script> tag. No Node.js runtime needed in production.

Config per flavor

SettingGoLaravel / PHPRailsDjango / FlaskPlain HTML/JS
build.swOutput"static""public""public""static""dist"
build.swoffPath"static/swoff""public/swoff""public/swoff""static/swoff""public/swoff"

| build.swUrl | "/swoff.sw.js" | "/swoff.sw.js" | "/swoff.sw.js" | "/swoff.sw.js" | "/swoff.sw.js" | | navigation.mode | "default" | "default" | "default" | "default" | "spa" | | navigation.fallback | "/offline" | "/offline" | "/offline" | "/offline" | "/index.html" | | strategy.default | "cache-first" | "cache-first" | "cache-first" | "cache-first" | "cache-first" |


Go

Where to place generated files

Set build.swoffPath to "static/swoff" — generated files land directly in your static directory:

npx @swoff/cli generate

Embed in binary

package main

import (
  "embed"
  "net/http"
)

//go:embed static/swoff/*
var swoffFS embed.FS

func main() {
  http.Handle("/swoff/", http.FileServer(http.FS(swoffFS)))
  http.ListenAndServe(":8080", nil)
}

Serve from disk (chi / gin)

// chi
r.Handle("/swoff/*", http.StripPrefix("/swoff/",
  http.FileServer(http.Dir("static/swoff"))))

// gin
r.Static("/swoff", "./static/swoff")

Entry point

<body>
  {{template "content" .}}
  <script src="/swoff/client-injector.bundle.js"></script>
</body>

Build (Makefile)

build:
	go build -o app .
	node static/swoff/sw/generator.mjs

Auth

Cookie-based session middleware (e.g., gorilla/sessions):

{
  "features": {
    "auth": {
      "enabled": true,
      "type": "cookie",
      "routePaths": ["/login", "/logout", "/api/auth/*"]
    }
  }
}

Laravel / PHP

Where to place generated files

Set build.swoffPath to "public/swoff" — generated files land in your public directory:

npx @swoff/cli generate

Entry point (Blade)

{{-- resources/views/layouts/app.blade.php --}}
<body>
  @yield('content')
  <script src="{{ asset('swoff/client-injector.bundle.js') }}"></script>
</body>

Build

Add to your composer.json scripts or CI pipeline:

node public/swoff/sw/generator.mjs

If using Vite or Laravel Mix:

npm run build && node swoff/sw/generator.mjs

Auth (Laravel Sanctum)

{
  "features": {
    "auth": {
      "enabled": true,
      "type": "cookie",
      "routePaths": ["/login", "/logout", "/api/*"]
    }
  }
}

The generated auth/adapter.ts works with Laravel's session-based auth out of the box — cookies are sent automatically, no token management needed.


Rails

Where to place generated files

Set build.swoffPath to "public/swoff" — generated files land in your public directory:

npx @swoff/cli generate

Entry point (ERB)

<body>
  <%= yield %>
  <script src="/swoff/client-injector.bundle.js"></script>
</body>

Build

# In your Rakefile or CI:
node public/swoff/sw/generator.mjs

If using a JS build tool (Vite, esbuild, Bun):

bun run build && node swoff/sw/generator.mjs
{
  "features": {
    "auth": {
      "enabled": true,
      "type": "cookie",
      "routePaths": ["/login", "/logout", "/session/*"]
    }
  }
}

Rails uses httpOnly session cookies by default — the browser sends them automatically.


Django

Where to place generated files

Set build.swoffPath to "your_app/static/swoff" — generated files land directly in your static directory:

npx @swoff/cli generate

Entry point (Jinja)

{% load static %}
<body>
  {% block content %}{% endblock %}
  <script src="{% static 'swoff/client-injector.bundle.js' %}"></script>
</body>

Build

# In CI or deployment:
node your_app/static/swoff/sw/generator.mjs
python manage.py collectstatic

Without a JS build step:

node your_app/static/swoff/sw/generator.mjs
python manage.py collectstatic

Auth (Django session)

{
  "features": {
    "auth": {
      "enabled": true,
      "type": "cookie",
      "routePaths": ["/login", "/logout", "/admin/*"]
    }
  }
}

Flask

Where to place generated files

Flask serves static/ automatically. Set build.swoffPath to "static/swoff":

npx @swoff/cli generate
# Files land directly in static/swoff/ — no copy needed

Entry point (Jinja)

<body>
  {% block content %}{% endblock %}
  <script src="{{ url_for('static', filename='swoff/client-injector.bundle.js') }}"></script>
</body>

Build

No collectstatic equivalent — generated files go straight into static/:

node static/swoff/sw/generator.mjs

If swOutput points to Flask's static folder and swoffPath to the same directory, run only the generator.

Auth (Flask session)

{
  "features": {
    "auth": {
      "enabled": true,
      "type": "cookie",
      "routePaths": ["/login", "/logout", "/api/auth/*"]
    }
  }
}

Plain HTML / JS

Where to place generated files

Set build.swoffPath to "public/swoff" — generated files land directly in your document root:

npx @swoff/cli generate

Entry point

<!DOCTYPE html>
<html>
  <head>
    <title>My Site</title>
  </head>
  <body>
    <h1>Hello</h1>
    <script src="/swoff/client-injector.bundle.js"></script>
  </body>
</html>

Build

# After building your static site:
node public/swoff/sw/generator.mjs

For projects without a build step, run the generator manually or add it to your deployment script.


Auth (general)

Cookie-based session auth works with all backend frameworks. The browser sends cookies automatically:

{
  "features": {
    "auth": {
      "enabled": true,
      "type": "cookie",
      "routePaths": ["/login", "/logout", "/api/auth/*"]
    }
  }
}

Cookie auth is compatible with background sync and server push.

On this page