Skip to content

Updating visuals

If you see any images containing outdated UI, please bear with us.

We are updating all content as quickly as possible to mirror our new UI.

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.

  1. 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.
  2. 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 template

Each 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

  1. SEO Benefits: Search engines see correct metadata immediately without needing to execute JavaScript.
  2. Social Media Sharing: Platforms like Facebook and LinkedIn scrape the initial HTML for Open Graph tags. If the correct index.html isn't served, the preview card will be wrong.
  3. 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 exist
  • RewriteCond %{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:

  1. Apache checks: Does /product/5/details exist? → No
  2. Matches rule: ^product/([^/]+)/details/?$ → Yes
  3. 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:

  1. Apache checks: Does /products directory exist? → Yes
  2. 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:

  1. Apache checks: Does file exist? → Yes
  2. 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.conf

2. 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.sh

4. Restart Apache

bash
sudo apachectl restart

5. 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_rewrite is enabled: grep "LoadModule rewrite_module" /etc/apache2/httpd.conf
  • Verify .htaccess is in the document root
  • Ensure AllowOverride All is set

Changes not taking effect?

bash
# Test configuration
sudo apachectl configtest

# Restart Apache
sudo apachectl restart

Solutions 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:

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   200

This 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:

Vercel routing example

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:

Netlify routing example

The code inside the netlify.toml would be something like this:

toml
[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200

Docs: 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 = 200

Nginx

For Nginx, a minimal configuration to handle basic SPA fallback looks like this:

nginx
location / {
  try_files $uri /index.html;
}

Troubleshooting

SymptomFix
Refresh still 404sEnsure rewrite file is at project root, not /public
Static assets breakAdd an exception for /assets/ or generated file paths
API routes get redirectedAdd 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.