Making Framework7 Routes Bookmarkable with pushState
Learn how to make Framework7 routes bookmarkable by enabling pushState and configuring a server fallback for deep links.
20 Sept 2026, 21:38 UTC

Problem: Direct URL loads break your Framework7 SPA
When you build a single‑page app with Framework7, navigating via f7-link or router.navigate works smoothly, but if a user refreshes the page or shares a link like https://example.com/about the server returns a 404. The app’s client‑side router never sees the request, so the URL is not bookmarkable.
Thesis: Enable pushState and configure the server to fallback to index.html
Framework7’s router can update the browser URL without a full reload when pushState is enabled. Combined with a server that returns index.html for any unknown path, the app can handle deep links, browser back/forward buttons, and shareable URLs.
Section 1: Basic router setup with pushState
First, create the Framework7 instance and tell the router to use pushState. The router configuration is a plain array of route objects; each object needs at least a path and a component.
import Framework7 from 'framework7/framework7.esm.bundle.js';
const app = new Framework7({
root: '#app',
id: 'com.example.myapp',
theme: 'auto',
// Enable pushState so URL changes without reload
routes: [
{ path: '/', component: 'home', url: './index.html' },
{ path: '/about/', component: 'about', url: './about.html' },
],
// Optional: pass props to components
routes: [
{ path: '/user/:id/', component: 'user', props: { title: 'User Profile' } }
]
});
// Access the router instance
const router = app.router;
Each route points to a local HTML template (or a component defined elsewhere). When the URL matches a path, Framework7 swaps in the corresponding template.
Section 2: Declarative navigation with f7-link
In your markup, use the f7-link element (or a with the link class) to trigger navigation. The router intercepts the click, updates the URL via pushState, and loads the new component.
<div class="page">
<div class="navbar">
<div class="navbar-inner">
<div class="title">My App</div>
</div>
</div>
<div class="page-content">
<a href="/about/" class="link button-raised">About</a>
<a href="/" class="link button-raised">Home</a>
</div>
</div>
Clicking these links changes the address bar without a full page reload.
Section 3: Handling deep links and server fallback
For a URL like https://example.com/about/ to work on refresh, the server must return the app’s index.html for any path that does not map to a static file. Below are examples for common dev servers.
- Express (Node.js):
const express = require('express');
const path = require('path');
const app = express();
app.use(express.static(path.join(__dirname, 'www')));
// fallback for SPA routes
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'www', 'index.html'));
});
app.listen(3000);
- nginx:
location / {
try_files $uri $uri/ /index.html;
}
With this fallback, a direct request to /about/ serves index.html, the router reads the current URL, and renders the About component.
Section 4: Trade‑offs and limitations
While pushState gives you bookmarkable URLs, there are a few practical considerations:
- Scroll restoration: Framework7 does not automatically scroll to the top after a route change. If you need that behavior, listen to
routeChangeand callwindow.scrollTo(0, 0). - Component state: Local data inside a component persists across navigations unless you reset it in hooks like
pageBeforeInorpageBeforeOut. Stale data can cause UI bugs. - Server configuration: You must control the server (or use a dev server that supports fallback). Static hosting without rewrite rules will still produce 404s for deep links.
Worked example: Verifying the setup
Follow these steps to confirm that pushState and deep linking work as expected. No output is guaranteed; you should observe the described behavior in your own environment.
- Create a minimal project with the code above, serving the
wwwfolder via Express or nginx with the fallback rule. - Open
http://localhost:3000/in a browser. Click the About link; the URL should change tohttp://localhost:3000/about/without a reload. - Press the browser back button; the URL should revert to
http://localhost:3000/and the Home view reappear. - Manually enter
http://localhost:3000/about/in the address bar and reload. The About page should load directly. - Open DevTools → Application → History. Each navigation should add a new entry with the correct URL and a state object containing the route name.
- Add a listener to verify events:
router.on('routeChange', (route, router) => {
console.log('Route changed to:', route.name);
});
You should see the log fire for link clicks, back/forward navigation, and on initial page load when a deep link is used.
Actionable closing
Enable pushState in your Framework7 router, configure your server to fallback to index.html, and use the lifecycle events to manage scroll and state. With these steps your SPA becomes truly bookmarkable, shareable, and friendly to the browser’s history.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.