Routing on Swarm
Swarm does not behave like a traditional web server — there is no server-side routing, and every route must correspond to a real file inside the site manifest.
If you try to use typical "clean URLs" like:
/about
/contact
/dashboard/settings
Swarm will look for literal files such as:
about
contact
dashboard/settings
...which don’t exist if you've uploaded a static website with files like about.html and contact.html.
There are two main strategies for addressing routing:
- Hash-Based Client-Side Routing
- Manifest-Based Routing with Aliases or Index Files
Now let’s look at each method:
Client-Side Hash Routing
This section explains how to add hash based client side routing to your Swarm hosted site so that you can have clean URLs for each page of your website.
See the routing project in the examples repo for a full working example implementation.
Swarm has no server backend running code and so can’t rewrite paths. One approach to routing is to set up a SPA with React's HashRouter, which keeps all routing inside the browser.
You can do this easily using a template from create-swarm-app and then adding your own pages.
1. Create a New Vite + React Project (with create-swarm-app)
Run:
npm init swarm-app@latest my-dapp vite-tsx
This generates a clean project containing:
src/
App.tsx
index.tsx
config.ts
public/
index.html
package.json
You now have a fully working Vite/React app ready for Swarm uploads.
2. Install React Router
Navigate to the project directory:
cd my-dapp
Inside the project:
npm install react-router-dom
This gives you client-side navigation capability.
3. Switch the App to Use Hash-Based Routing
Swarm only serves literal files, so /#/about is the only reliable way to have “pages.”
Replace your ./src/App.tsx with:
import { HashRouter, Routes, Route, Link } from 'react-router-dom'
import { Home } from './Home'
import { About } from './About'
import { NotFound } from './NotFound'
export function App() {
return (
<HashRouter>
<nav style={{ display: 'flex', gap: '12px', padding: '12px' }}>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="*" element={<NotFound />} />
</Routes>
</HashRouter>
)
}
This gives you usable routes:
/#/ → Home
/#/about → About
/#/anything → React 404 page
4. Add Your Page Components
Create your page components inside ./src:
Example Home.tsx:
export function Home() {
return (
<div style={{ padding: '20px' }}>
<h1>Home</h1>
<p>Welcome to your Swarm-powered app.</p>
</div>
)
}
Example About.tsx:
export function About() {
return (
<div style={{ padding: '20px' }}>
<h1>About</h1>
<p>This demo shows how to upload files or directories to Swarm using Bee-JS.</p>
</div>
)
}
Example NotFound.tsx:
export function NotFound() {
return (
<div style={{ padding: '20px' }}>
<h1>Page Not Found</h1>
<a href="./#/">Return to Home</a>
</div>
)
}
5. Add a Static 404.html for Non-Hash URLs
Swarm still needs a fallback for URLs like:
/non-existent-file
Create a ./public directory and save a 404.html file inside:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>404 – Not Found</title>
<style>
body { font-family: sans-serif; padding: 40px; }
a { color: #007bff; }
</style>
</head>
<nav style="display: flex; gap: 12px; padding: 12px">
<a href="./#/">Home</a>
<a href="./#/about">About</a>
</nav>
<body>
<h1>404</h1>
<p>This page doesn't exist.</p>
<p><a href="./#/">Return to Home</a></p>
</body>
</html>
Vite will automatically include this in dist/.
This file handles non-hash missing paths. React handles hash missing paths.
6. Build the Project
Before uploading, compile the Vite app into a static bundle:
npm run build
This produces a dist/ folder containing:
dist/
index.html
404.html
assets/
Everything inside dist/ will be uploaded to your Swarm feed.
7. Create a Publisher Identity and Deploy Using a Feed Manifest
A feed manifest gives your site a stable Swarm URL that always points to the latest version. You upload your site, publish its reference to a feed, and then use the feed manifest hash as your permanent URL.
Deploy Using bee-js
You can perform the same steps with swarm-cli:
- Linux / macOS
- Windows PowerShell
swarm-cli identity create web-publisher
swarm-cli feed upload ./dist \
--identity web-publisher \
--topic-string website \
--stamp <BATCH_ID> \
--index-document index.html \
--error-document 404.html
swarm-cli identity create web-publisher
swarm-cli feed upload .\dist `
--identity web-publisher `
--topic-string website `
--stamp <BATCH_ID> `
--index-document index.html `
--error-document 404.html
The output includes the site hash, the feed manifest URL (your permanent URL), and postage stamp details.
Example:
http://localhost:1633/bzz/<feed-manifest-hash>/
8. Visit Your Site
-
Home:
/#/ -
About:
/#/about -
Invalid hash route: handled by
NotFound.tsx -
Invalid non-hash route: handled by
404.html
Summary
You now have:
- A Vite + React app
- Hash-based routing fully compatible with Swarm
- A static 404 for non-hash paths
- A React 404 for invalid hash paths
- Stable, versioned deployments using feed manifests
Manifest Based Routing
The second routing method involves directly manipulating the manifest so that routes resolve properly to the intended content. There are two primary approaches to manifest based routing:
- Alias based routing with arbitrary file names
- Directory based with index files
1. Upload the Site
Start by uploading the site:
const { reference } = await bee.uploadFilesFromDirectory(batchId, './site')
Without manifest edits, routes only work via exact file paths like:
/index.html
/about.html
/contact.html
Trying to access /about or /about/ will fail.
2. Fix Routing With Manifest Manipulation
You can fix clean URLs using two strategies:
Strategy A: Add Aliases to the Manifest
node.addFork('about', referenceForAbout, metadata)
node.addFork('about/', referenceForAbout, metadata)
Now /about and /about/ work like /about.html.
Strategy B: Use Directory Index Files
Restructure files like:
/about/index.html
/contact/index.html
And add them to the manifest like:
node.addFork('about/', referenceForAboutIndex, metadata)
This gives you full directory-style clean URLs.
Remove and Redirect Routes
To "delete" a page you would need to remove all entries for it from the manifest to remove it entirely:
node.removeFork('old-page.html')
For example, if you had manually added:
node.addFork('about', referenceForAbout, metadata)
node.addFork('about/', referenceForAbout, metadata)
You will need to make sure to fully remove all entries for that file from the manifest to really "delete" it from your site.
node.removeFork('about')
node.removeFork('about/')
node.removeFork('about.html')
The file is not removed from the Swarm network since data on Swarm is immutable. Anyone who has its reference can still retrieve it. Removing it from your manifest just means it's no longer available through a route on your site.
We are adding a record with the same path but now pointing at new content to achieve "redirect" like behavior.
To redirect:
node.addFork('old-page.html', newPageReference, metadata)
This lets you “deprecate” a page and point old paths to new ones.
4. Manifest Routing Enables Dynamic Content
Once you understand manifest-based routing, you can dynamically:
- Add new paths (e.g. blog posts, product pages)
- Create custom routes
- Redirect old paths
- Remove unwanted paths
Next, learn how to combine all the previously covered concepts to enable dynamic content on Swarm. It will allow you to turn Swarm into a decentralized CMS and decouple your front end from your back end.