Skip to content

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:

javascript
// vite.config.js
import { defineConfig } from 'vite';
import { sigproRouter } from 'sigpro';

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

Once you have the generated routes, using them with the router is straightforward:

javascript
// main.js
import { $, html } from 'sigpro';
import { routes } from 'virtual:sigpro-routes';

// Simple usage
const router = $.router(routes);
document.body.appendChild(router);

Or directly in your template:

javascript
// app.js
import { $, html } from 'sigpro';
import { routes } from 'virtual:sigpro-routes';

const App = () => html`
  <div class="app">
    <header>
      <h1>My Application</h1>
    </header>
    
    <main class="p-4 flex flex-col gap-4 mx-auto w-full">
      <div class="p-4 bg-base-100 rounded-box shadow-sm">
        ${$.router(routes)}
      </div>
    </main>
  </div>
`;

document.body.appendChild(App());

This approach keeps your template clean and lets the router handle all the page rendering automatically.

3. Create Pages

javascript
// src/pages/index.js
import { $, html } from 'sigpro';

export default () => {
  return html`
    <div>
      <h1>Home Page</h1>
      <a href="#/about">About</a>
    </div>
  `;
};
javascript
// 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 PathGenerated 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 PathGenerated RouteExample 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 PathGenerated RouteNotes
src/pages/settings/index.js/settingsIndex page
src/pages/settings/profile.js/settings/profileSub-page
src/pages/settings/security.js/settings/securitySub-page
src/pages/settings/[section].js/settings/:sectionDynamic section

🎯 Advanced Examples

Blog with Posts

javascript
// 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>
  `;
};
javascript
// 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

javascript
// 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>
  `;
};
javascript
// 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

javascript
// 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> &gt;
        <a href="#/products/${category}">${category}</a> &gt;
        <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:

javascript
// 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

OptionTypeDefaultDescription
pagesDirstring'src/pages'Directory containing your pages
extensionsstring[]['.js', '.jsx']File extensions to include
excludestring[][]Glob patterns to exclude

🎯 Route Priority

The plugin automatically sorts routes to ensure correct matching:

  1. Static routes take precedence over dynamic ones
  2. More specific routes (deeper paths) come first
  3. 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:

javascript
// 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
javascript
// With dynamic imports (automatic with Vite)
const routes = [
  { path: '/', component: () => import('./pages/index.js') },
  { path: '/about', component: () => import('./pages/about.js') },
  // ...
];

💡 Pro Tips

src/pages/
├── dashboard/
│   ├── index.js
│   ├── analytics.js
│   └── settings.js
└── dashboard.js   # ❌ Don't mix with folder

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

4. Layout Components

Create a layout wrapper in your main entry:

javascript
// 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!