Appearance
Self-hosting with dynamic URL path parameters
WeWeb apps are Vue.js Single‑Page Applications (SPAs).
When deploying to static hosting providers such as Cloudflare, Netlify, or Vercel, you may notice:
- ✔️ Navigating to a page with a dynamic path parameter works (e.g.,
/category → /category/{variable}) - ❌ Refreshing a page with a dynamic path parameter returns a 404
This is not a bug in your code — it’s a routing configuration issue.
In Simple Terms
Think of your WeWeb app as a hotel where index.html is the Reception Desk.
- When you click links inside the app: You are already in the hotel. The receptionist guides you to different rooms (pages) instantly. You don't need to ask the hotel security (the server) for permission.
- When you refresh or share a link: You are arriving from outside. You ask the security guard for specific access (e.g. "Room 101").
- The Problem: In a Single-Page Application (SPA), "Room 101" doesn't technically exist as a separate physical room; it's created virtually by the receptionist. The guard checks his list, doesn't see "Room 101", and says "404 Not Found".
- The Solution: You give the guard a new instruction: "If someone asks for a room number I don't know, just send them to the Reception Desk (
index.html). The receptionist will handle it."
Why does this matter for SEO? Some visitors are robots (like Google or Facebook). They read the page title before the receptionist (JavaScript) starts working. If you just send them to the main reception, they might see "Home Page" instead of "Product 101". The advanced configurations below ensure these robots see the correct "Product 101" title immediately.
Understanding Your Build Structure
The build you get with your Vue.js application will have an index.html file at the root and other index files for every page. Usually, the root index is called when you start from the home page, but you may need to access a page directly. In that case, the specific index.html file for that page must be served.
Your Build Output Structure
After running npm run build, your dist folder contains:
dist/
├── index.html # Homepage (root)
├── assets/ # JavaScript, CSS, fonts
├── data/ # JSON data files
├── products/
│ └── index.html # Products listing page
└── product/
└── :param/
└── details/
└── index.html # Product details page templateEach index.html file contains:
- Specific metadata (title, description, Open Graph tags)
- The same Vue.js application code (in
assets/main-*.js) - Different initial page data
The Challenge: URLs with Parameters
Notice the :param folder in the structure above? This is a literal folder name, not a dynamic parameter. It serves as a template for all product pages.
You have:
- One template file:
/product/:param/details/index.html - Many possible URLs:
/product/1/details,/product/5/details,/product/99/details, etc.
When a user directly visits /product/5/details:
Without proper routing:
- ❌ Server looks for
/product/5/details/index.html→ doesn't exist → 404 error - ❌ Wrong metadata loaded (or none at all)
- ❌ Search engines and social media see incorrect information
With proper routing:
- ✅ Server maps
/product/5/details→/product/:param/details/index.html - ✅ Correct metadata served immediately
- ✅ SEO and social sharing work perfectly
- ✅ Vue app loads and renders product #5
Why This Matters
- SEO Benefits: Search engines see correct metadata immediately without needing to execute JavaScript.
- Social Media Sharing: Platforms like Facebook and LinkedIn scrape the initial HTML for Open Graph tags. If the correct
index.htmlisn't served, the preview card will be wrong. - User Experience: Direct links work, and page refreshes maintain the correct route.
The Solution
You need a specific routing rule to ensure the proper index.html is loaded when the URL contains a parameter.
Example: Apache Server Configuration
Below you'll find an example of a .htaccess file for an Apache server that handles this routing correctly. Place this file in your web server's document root (where your index.html is located).
The .htaccess File
apache
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
# Don't rewrite direct index.html requests
RewriteRule ^index\.html$ - [L]
# Map dynamic product detail URLs to the static :param template
# Example: /product/5/details -> /product/:param/details/index.html
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^product/([^/]+)/details/?$ /product/:param/details/index.html [L]
# Map dynamic profile URLs to the static :param template
# Example: /profile/5 -> /profile/:param/index.html
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^profile/([^/]+)/?$ /profile/:param/index.html [L]
# Fallback: serve root index.html for any other non-existent paths
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>How It Works
1. Enable rewriting
apache
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /Checks if Apache's mod_rewrite module is enabled and turns on URL rewriting.
2. Preserve direct requests
apache
RewriteRule ^index\.html$ - [L]If someone directly requests index.html, serve it without modification.
3. Handle parameterized URLs
apache
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^product/([^/]+)/details/?$ /product/:param/details/index.html [L]Breaking it down:
RewriteCond %{REQUEST_FILENAME} !-f- Only apply if the file doesn't existRewriteCond %{REQUEST_FILENAME} !-d- Only apply if it's not a directory^product/- Match URLs starting with "product/"([^/]+)- Capture any characters except "/" (the product ID: 1, 5, 99, etc.)/details/?$- Match "/details" or "/details/" at the end/product/:param/details/index.html- Serve this template file[L]- Stop processing (Last rule)
Example flow:
User visits: /product/5/details
↓
Apache checks: Does /product/5/details file exist? → No
Does /product/5/details directory exist? → No
↓
Apache matches: ^product/([^/]+)/details/?$ → Yes! (captures "5")
↓
Apache serves: /product/:param/details/index.html
↓
Result: Correct metadata loaded! ✅4. Fallback rule
apache
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]Any URL that doesn't match previous rules and doesn't exist as a file/directory gets the root index.html. This allows Vue Router to handle client-side routing.
Real-World Example
Product Detail Page with Parameter
Request (local testing): http://localhost:8080/product/5/details
What happens:
- Apache checks: Does
/product/5/detailsexist? → No - Matches rule:
^product/([^/]+)/details/?$→ Yes - Serves:
/product/:param/details/index.html
HTML served:
html
<!DOCTYPE html>
<html>
<head>
<title>Detailed page for one product</title>
<meta name="description" content="Description for product detailed page">
<meta property="og:title" content="Detailed page for one product">
<!-- ✅ Correct metadata loaded immediately! -->
</head>
<body>
<div id="app"></div>
<script src="/assets/main-Bdry9Nph.js"></script>
<!-- Vue app loads and renders product #5 -->
</body>
</html>Static Directory Page
Request (local testing): http://localhost:8080/products
What happens:
- Apache checks: Does
/productsdirectory exist? → Yes - Serves:
/products/index.html(directory index)
No rewrite rule needed - Apache handles this automatically.
Static Asset
Request (local testing): http://localhost:8080/assets/main.js
What happens:
- Apache checks: Does file exist? → Yes
- Serves the file directly
No rewriting occurs for existing files.
Why This Matters
SEO Benefits
- Search engines see correct metadata immediately (no JavaScript execution needed)
- Each page indexed with unique titles and descriptions
- Better search rankings
Social Media Sharing
- Facebook, Twitter, LinkedIn see proper Open Graph tags
- Correct preview cards with images, titles, descriptions
- Different metadata for each product
User Experience
- Direct links work (bookmarks, emails, etc.)
- Page refreshes maintain correct route
- Browser back/forward buttons work
- Fast load times (static files)
Quick Setup Guide
1. Enable mod_rewrite
bash
sudo sed -i '' 's/#LoadModule rewrite_module/LoadModule rewrite_module/' /etc/apache2/httpd.conf2. Enable .htaccess files
Ensure your Apache virtual host config has:
apache
<Directory "/Library/WebServer/Documents">
AllowOverride All
</Directory>3. Build and deploy
bash
npm run build
./deploy-app.sh4. Restart Apache
bash
sudo apachectl restart5. Test (for local setup)
bash
# Test parameterized URL on your local machine
curl http://localhost:8080/product/5/details | grep "<title>"
# Should show: <title>Detailed page for one product</title>Troubleshooting
404 errors on parameterized URLs?
- Check if
mod_rewriteis enabled:grep "LoadModule rewrite_module" /etc/apache2/httpd.conf - Verify
.htaccessis in the document root - Ensure
AllowOverride Allis set
Changes not taking effect?
bash
# Test configuration
sudo apachectl configtest
# Restart Apache
sudo apachectl restartSolutions For other providers
The solution depends on your provider. To fix this, you need a specific routing rule to ensure the proper index.html is loaded.
Cloudflare
To self‑host your WeWeb app on Cloudflare, we recommend using Cloudflare Pages.
If you deploy a static site without adding a 404.html, Cloudflare Pages may detect you are deploying an SPA and will serve your index.html for paths that don’t match a file. As a result, the exported WeWeb app can work as is, with no manual custom routing needed on your side.
We have documented the step‑by‑step process here:
- Cloudflare guide: https://docs.weweb.io/settings-billing-code-export/cloudflare-self-hosting-guide.html
Pro tip: If you add a 404.html or you want to make SPA fallback explicit, include a _redirects file at the project root:
txt
/* /index.html 200This ensures any unknown path is served index.html, letting the Vue router handle it.
Vercel
To properly serve pages with dynamic path parameters when you self‑host your WeWeb app on Vercel, add a vercel.json file at the root of your exported app:

The code inside the vercel.json would be something like this:
json
{
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
}This snippet will work for a straightforward use case. However, for advanced SEO optimization of dynamic routes (serving the :param template instead of root), you may need to add more specific rewrite rules matching your URL patterns.
Docs: https://vercel.com/docs/project-configuration#rewrites
If you also have API routes and want to exclude them from the SPA fallback, put those rules first:
json
{
"rewrites": [
{ "source": "/api/(.*)", "destination": "/api/$1" },
{ "source": "/(.*)", "destination": "/index.html" }
]
}Netlify
To properly serve pages with dynamic path parameters when you self‑host your WeWeb app on Netlify, add a netlify.toml file at the root of your exported app:

The code inside the netlify.toml would be something like this:
toml
[[redirects]]
from = "/*"
to = "/index.html"
status = 200Docs: https://docs.netlify.com/manage/routing/redirects/overview/
If you also have API routes and want to exclude them from the SPA fallback, add those rules above the fallback:
toml
[[redirects]]
from = "/api/*"
to = "/api/:splat"
status = 200
[[redirects]]
from = "/*"
to = "/index.html"
status = 200Nginx
For Nginx, a minimal configuration to handle basic SPA fallback looks like this:
nginx
location / {
try_files $uri /index.html;
}Troubleshooting
| Symptom | Fix |
|---|---|
| Refresh still 404s | Ensure rewrite file is at project root, not /public |
| Static assets break | Add an exception for /assets/ or generated file paths |
| API routes get redirected | Add rules before the SPA fallback |
| Wrong metadata showing? | Verify the template file exists in your build output (e.g. /product/:param/details/index.html) and check your rewrite rules map to it correctly. |
| Changes not taking effect? | Restart your web server or clear your CDN cache. |
Support policy for self‑hosting
- WeWeb does not provide support for custom self‑hosting setups as part of standard plans.
- Because hosting environments vary widely (Vercel, Netlify, Cloudflare, NGINX, Apache, etc.), any issues related to hosting configuration, routing rules, or infrastructure must be handled by your hosting provider.
- We can only guarantee full support when deploying through WeWeb Hosting.
- For customers on an Enterprise plan, WeWeb can provide guidance and best‑effort assistance for self‑hosting deployments.

