Vite Plugin: Automatic File-based Routing 🚦
SigPro provides an optional Vite plugin that automatically generates routes based on your file structure. No configuration needed - just create pages and they're instantly available with the correct paths.
Why Use This Plugin?
While SigPro's router works perfectly with manually defined routes, this plugin:
- Eliminates boilerplate - No need to write route configurations
- Enforces conventions - Consistent URL structure across your app
- Supports dynamic routes - Use
[param]syntax for parameters - Automatic code-splitting - Each page becomes a separate chunk
- Type-safe (with JSDoc) - Routes follow your file structure
Installation
The plugin is included with SigPro, but you need to add it to your Vite config:
// vite.config.js
import { defineConfig } from 'vite';
import { sigproRouter } from 'sigpro/vite';
export default defineConfig({
plugins: [sigproRouter()]
});How It Works
The plugin scans your src/pages directory and automatically generates routes based on the file structure:
src/pages/
├── index.js → '/'
├── about.js → '/about'
├── blog/
│ ├── index.js → '/blog'
│ └── [slug].js → '/blog/:slug'
└── users/
├── [id].js → '/users/:id'
└── [id]/edit.js → '/users/:id/edit'Usage
1. Enable the Plugin
Add the plugin to your Vite config as shown above.
2. Import the Generated Routes
// main.js
import { $, html } from 'sigpro';
import { routes } from 'virtual:sigpro-routes';
// Use the generated routes directly
const router = $.router(routes);
document.body.appendChild(router);3. Create Pages
// src/pages/index.js
import { $, html } from 'sigpro';
export default () => {
return html`
<div>
<h1>Home Page</h1>
<a href="#/about">About</a>
</div>
`;
};// src/pages/users/[id].js
import { $, html } from 'sigpro';
export default (params) => {
const userId = params.id;
return html`
<div>
<h1>User Profile: ${userId}</h1>
<a href="#/users/${userId}/edit">Edit</a>
</div>
`;
};📋 File-to-Route Mapping
Static Routes
| File Path | Generated Route |
|---|---|
src/pages/index.js | / |
src/pages/about.js | /about |
src/pages/contact/index.js | /contact |
src/pages/blog/post.js | /blog/post |
Dynamic Routes
| File Path | Generated Route | Example URL |
|---|---|---|
src/pages/users/[id].js | /users/:id | /users/42 |
src/pages/blog/[slug].js | /blog/:slug | /blog/hello-world |
src/pages/users/[id]/posts/[pid].js | /users/:id/posts/:pid | /users/42/posts/123 |
Nested Routes
| File Path | Generated Route | Notes |
|---|---|---|
src/pages/settings/index.js | /settings | Index page |
src/pages/settings/profile.js | /settings/profile | Sub-page |
src/pages/settings/security.js | /settings/security | Sub-page |
src/pages/settings/[section].js | /settings/:section | Dynamic section |
🎯 Advanced Examples
Blog with Posts
// src/pages/blog/index.js - Lists all posts
export default () => {
const posts = $([]);
$.effect(() => {
fetch('/api/posts')
.then(res => res.json())
.then(data => posts(data));
});
return html`
<div>
<h1>Blog</h1>
${posts().map(post => html`
<article>
<h2><a href="#/blog/${post.slug}">${post.title}</a></h2>
<p>${post.excerpt}</p>
</article>
`)}
</div>
`;
};// src/pages/blog/[slug].js - Single post
export default (params) => {
const post = $(null);
const slug = params.slug;
$.effect(() => {
fetch(`/api/posts/${slug}`)
.then(res => res.json())
.then(data => post(data));
});
return html`
<div>
<a href="#/blog">← Back to blog</a>
${() => post() ? html`
<article>
<h1>${post().title}</h1>
<div>${post().content}</div>
</article>
` : html`<div>Loading...</div>`}
</div>
`;
};Dashboard with Nested Sections
// src/pages/dashboard/index.js
export default () => {
return html`
<div class="dashboard">
<nav>
<a href="#/dashboard">Overview</a>
<a href="#/dashboard/analytics">Analytics</a>
<a href="#/dashboard/settings">Settings</a>
</nav>
<main>
<h1>Dashboard Overview</h1>
<!-- Overview content -->
</main>
</div>
`;
};// src/pages/dashboard/analytics.js
export default () => {
return html`
<div class="dashboard">
<nav>
<a href="#/dashboard">Overview</a>
<a href="#/dashboard/analytics">Analytics</a>
<a href="#/dashboard/settings">Settings</a>
</nav>
<main>
<h1>Analytics</h1>
<!-- Analytics content -->
</main>
</div>
`;
};E-commerce Product Routes
// src/pages/products/[category]/[id].js
export default (params) => {
const { category, id } = params;
const product = $(null);
$.effect(() => {
fetch(`/api/products/${category}/${id}`)
.then(res => res.json())
.then(data => product(data));
});
return html`
<div class="product-page">
<nav class="breadcrumbs">
<a href="#/products">Products</a> >
<a href="#/products/${category}">${category}</a> >
<span>${id}</span>
</nav>
${() => product() ? html`
<div class="product">
<h1>${product().name}</h1>
<p class="price">$${product().price}</p>
<p>${product().description}</p>
<button @click=${() => addToCart(product())}>
Add to Cart
</button>
</div>
` : html`<div>Loading...</div>`}
</div>
`;
};🔧 Configuration Options
The plugin accepts an optional configuration object:
// vite.config.js
import { defineConfig } from 'vite';
import { sigproRouter } from 'sigpro/vite';
export default defineConfig({
plugins: [
sigproRouter({
pagesDir: 'src/pages', // Default: 'src/pages'
extensions: ['.js', '.jsx'], // Default: ['.js', '.jsx']
exclude: ['**/_*', '**/components/**'] // Glob patterns to exclude
})
]
});Options
| Option | Type | Default | Description |
|---|---|---|---|
pagesDir | string | 'src/pages' | Directory containing your pages |
extensions | string[] | ['.js', '.jsx'] | File extensions to include |
exclude | string[] | [] | Glob patterns to exclude |
🎯 Route Priority
The plugin automatically sorts routes to ensure correct matching:
- Static routes take precedence over dynamic ones
- More specific routes (deeper paths) come first
- Alphabetical order for routes at the same level
Example sorting:
/users/new (static, specific)
/users/[id]/edit (dynamic, deeper)
/users/[id] (dynamic, shallower)
/users/profile (static, shallower)📦 Output Example
When you import virtual:sigpro-routes, you get:
// Generated module
import Page_0 from '/src/pages/index.js';
import Page_1 from '/src/pages/about.js';
import Page_2 from '/src/pages/blog/index.js';
import Page_3 from '/src/pages/blog/[slug].js';
import Page_4 from '/src/pages/users/[id].js';
import Page_5 from '/src/pages/users/[id]/edit.js';
export const routes = [
{ path: '/', component: Page_0 },
{ path: '/about', component: Page_1 },
{ path: '/blog', component: Page_2 },
{ path: '/blog/:slug', component: Page_3 },
{ path: '/users/:id', component: Page_4 },
{ path: '/users/:id/edit', component: Page_5 },
];🚀 Performance Benefits
- Automatic code splitting - Each page becomes a separate chunk
- Lazy loading ready - Import pages dynamically
- Tree shaking - Only used routes are included
// With dynamic imports (automatic with Vite)
const routes = [
{ path: '/', component: () => import('./pages/index.js') },
{ path: '/about', component: () => import('./pages/about.js') },
// ...
];💡 Pro Tips
1. Group Related Pages
src/pages/
├── dashboard/
│ ├── index.js
│ ├── analytics.js
│ └── settings.js
└── dashboard.js # ❌ Don't mix with folder2. Use Index Files for Clean URLs
✅ Good:
pages/blog/index.js → /blog
pages/blog/post.js → /blog/post
❌ Avoid:
pages/blog.js → /blog (conflicts with folder)3. Private Components
Prefix with underscore to exclude from routing:
src/pages/
├── index.js
├── about.js
└── _components/ # ❌ Not scanned
└── Header.js4. Layout Components
Create a layout wrapper in your main entry:
// main.js
import { $, html } from 'sigpro';
import { routes } from 'virtual:sigpro-routes';
// Wrap all routes with layout
const routesWithLayout = routes.map(route => ({
...route,
component: (params) => Layout(route.component(params))
}));
const router = $.router(routesWithLayout);
document.body.appendChild(router);Note: This plugin is completely optional. You can always define routes manually if you prefer. The plugin just saves you from writing boilerplate route configurations.
Pro Tip: The plugin works great with hot module replacement (HMR) - add a new page and it's instantly available in your dev server without restarting!