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
| Setting | Go | Laravel / PHP | Rails | Django / Flask | Plain 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 generateEmbed 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.mjsAuth
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 generateEntry 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.mjsIf using Vite or Laravel Mix:
npm run build && node swoff/sw/generator.mjsAuth (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 generateEntry point (ERB)
<body>
<%= yield %>
<script src="/swoff/client-injector.bundle.js"></script>
</body>Build
# In your Rakefile or CI:
node public/swoff/sw/generator.mjsIf using a JS build tool (Vite, esbuild, Bun):
bun run build && node swoff/sw/generator.mjsAuth (cookie sessions)
{
"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 generateEntry 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 collectstaticWithout a JS build step:
node your_app/static/swoff/sw/generator.mjs
python manage.py collectstaticAuth (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 neededEntry 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.mjsIf 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 generateEntry 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.mjsFor 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.