npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

solid-file-router

v0.1.6

Published

Type safe file router for solid.js

Readme

solid-file-router

Type safe file router for solid.js

Generate type safe route definition and virtual module that return @solidjs/router's RouteDefinition and <FileRouter />

ESM Only

Features

  • 📁 File-based routing - Automatically generates routes from your src/pages/** directory structure
  • 🔒 Type-safe - Full TypeScript support with generated type definitions for routes and path parameters
  • Vite integration - Works seamlessly with Vite as a plugin
  • 🎯 Flexible layouts - Support for _layout.tsx files to define nested layouts
  • 🛡️ Error boundaries - Built-in error handling with custom error components
  • 📦 Loading states - Optional loading components while data is being fetched

Getting Started

Installation

npm install solid-file-router
# or
yarn add solid-file-router
# or
bun add solid-file-router

Setup

  1. Add the Vite plugin to your vite.config.ts:
import { defineConfig } from 'vite'
import solid from 'vite-plugin-solid'
import { fileRouter } from 'solid-file-router/plugin'

export default defineConfig({
  plugins: [solid(), fileRouter()],
})
  1. Create your pages directory at src/pages/

  2. Create the app root (src/pages/_app.tsx): This serves as the root layout for your application.

import { createRoute } from 'solid-file-router'

export default createRoute({
  component: (props) => {
    return <div id="app-root">{props.children}</div>
  },
})
  1. Create your entry point (e.g., src/index.tsx):
import { render } from 'solid-js/web'
import { FileRouter } from 'virtual:routes'

render(() => <FileRouter base="/optional/base" />, document.getElementById('app')!)

Project Structure

Understanding the file structure is key to using the router effectively.

src/
  pages/
    _app.tsx              # App root (Required)
    index.tsx             # Matches: /
    about.tsx             # Matches: /about
    404.tsx               # Catch-all for unmatched routes

    # Nested Routes & Layouts
    blog/
      _layout.tsx         # Wraps all routes inside /blog/
      index.tsx           # Matches: /blog
      [id].tsx            # Matches: /blog/:id

    # Dynamic & Optional Params
    -[lang]/
      index.tsx           # Matches: /:lang?

    # Pathless Layouts (Logical grouping without URL change)
    (auth)/
      login.tsx           # Matches: /login
      register.tsx        # Matches: /register

    # Nested URLs without nested layouts
    path.to.some.url.tsx  # Matches: /path/to/some/url

  index.tsx               # Entry point
  routes.d.ts             # Auto-generated type definitions

API Reference & Examples

createRoute(config)

The core function to define route behavior. Must be the default export in every page file.

Parameters:

  • component (Required): Component to render.
  • preload (Optional): Async function to fetch data before rendering (@solidjs/router mechanism).
  • loadingComponent (Optional): Component shown while preload is pending.
  • errorComponent (Optional): Error Boundary component shown if rendering or preloading fails.
  • info (Optional): Arbitrary metadata.
  • matchFilters (Optional): Custom logic to validate route matching.

Component Inheritance:

When loadingComponent and errorComponent are defined in _app.tsx or _layout.tsx files, they automatically become defaults for all descendant routes. This follows a three-tier fallback chain:

  1. Route-specific - Component defined in the route's own createRoute()
  2. Nearest layout - Component from the closest _layout.tsx ancestor
  3. App default - Component from _app.tsx
  4. None - If not defined anywhere

This inheritance system reduces boilerplate while maintaining flexibility for route-specific overrides.

Example 1: Basic Page with Dynamic Params

File: src/pages/blog/[id].tsx

import { createRoute } from 'solid-file-router'
import { useParams } from '@solidjs/router'

export default createRoute({
  // Validate matches or extract custom data
  matchFilters: {
    id: (v) => /^\d+$/.test(v) // Only match if ID is numeric
  },
  component: (props) => {
    // Typesafe params if using the generated hooks/types
    const params = useParams<{ id: string }>()
    return <div>Viewing Post ID: {params.id}</div>
  },
})

Example 2: Data Loading, Loading States & Error Handling

File: src/pages/dashboard.tsx

import { createRoute } from 'solid-file-router'

export default createRoute({
  // Fetch data before the component renders
  preload: async ({ params, location }) => {
    const res = await fetch(`/api/stats`)
    if (!res.ok) throw new Error("Failed to load stats")
    return res.json()
  },

  // Show this while preload is awaiting
  loadingComponent: () => <div class="spinner">Loading Dashboard...</div>,

  // Show this if preload throws or component errors
  errorComponent: (props) => (
    <div class="error-alert">
      <p>Error: {props.error.message}</p>
      <button onClick={props.reset}>Retry</button>
    </div>
  ),

  // Main component receives data from preload via props.data
  component: (props) => (
    <main>
      <h1>Dashboard</h1>
      <pre>{JSON.stringify(props.data, null, 2)}</pre>
    </main>
  ),
})

Example 3: Nested Layouts

File: src/pages/settings/_layout.tsx

import { createRoute } from 'solid-file-router'
import { A } from '@solidjs/router'

export default createRoute({
  component: (props) => (
    <div class="settings-layout">
      <nav>
        <A href="/settings/profile">Profile</A>
        <A href="/settings/account">Account</A>
      </nav>
      <div class="content">
        {/* Renders the nested child route */}
        {props.children}
      </div>
    </div>
  ),
})

Component Inheritance

One of the most powerful features is automatic inheritance of loading and error components from layouts to routes. This eliminates repetitive configuration while maintaining full control when needed.

How It Works

When you define loadingComponent or errorComponent in _app.tsx or _layout.tsx, all descendant routes automatically inherit these components unless they provide their own.

Inheritance Priority (Fallback Chain):

  1. Route's own component (highest priority)
  2. Nearest _layout.tsx ancestor
  3. _app.tsx application default
  4. None (lowest priority)

Example: Application-Wide Defaults

File: src/pages/_app.tsx

import { createRoute } from 'solid-file-router'

export default createRoute({
  component: (props) => (
    <div id="app">
      <header>My App</header>
      <main>{props.children}</main>
    </div>
  ),
  
  // These become defaults for ALL routes
  loadingComponent: () => (
    <div class="loading-spinner">
      <div class="spinner" />
      <p>Loading...</p>
    </div>
  ),
  
  errorComponent: (props) => (
    <div class="error-page">
      <h1>Something went wrong</h1>
      <p>{props.error.message}</p>
      <button onClick={props.reset}>Try Again</button>
    </div>
  ),
})

Now every route in your app automatically gets these loading and error components without any additional configuration!

Example: Section-Specific Overrides

File: src/pages/dashboard/_layout.tsx

import { createRoute } from 'solid-file-router'

export default createRoute({
  component: (props) => (
    <div class="dashboard">
      <aside>Dashboard Nav</aside>
      <div class="dashboard-content">{props.children}</div>
    </div>
  ),
  
  // Override loading for all dashboard routes
  loadingComponent: () => (
    <div class="dashboard-loading">
      <div class="skeleton-layout" />
    </div>
  ),
  
  // errorComponent not specified - inherits from _app.tsx
})

Result:

  • All routes under /dashboard/* use the dashboard-specific loading component
  • All routes under /dashboard/* still use the app-wide error component from _app.tsx

Example: Route-Specific Override

File: src/pages/dashboard/analytics.tsx

import { createRoute } from 'solid-file-router'

export default createRoute({
  preload: async () => {
    const data = await fetch('/api/analytics').then(r => r.json())
    return data
  },
  
  // This route needs a special loading state
  loadingComponent: () => (
    <div class="analytics-loading">
      <div class="chart-skeleton" />
      <div class="stats-skeleton" />
    </div>
  ),
  
  // errorComponent not specified - inherits from _app.tsx
  
  component: (props) => (
    <div class="analytics">
      <h1>Analytics</h1>
      <pre>{JSON.stringify(props.data, null, 2)}</pre>
    </div>
  ),
})

Result:

  • This specific route uses its own custom loading component
  • Still inherits the error component from _app.tsx

Example: Complete Inheritance Chain

Here's a complete example showing how the three-tier fallback works:

src/pages/
  _app.tsx                    # Defines: loadingComponent, errorComponent
  dashboard/
    _layout.tsx               # Defines: loadingComponent (overrides app)
    index.tsx                 # Inherits: dashboard loading, app error
    users.tsx                 # Inherits: dashboard loading, app error
    analytics.tsx             # Defines: loadingComponent (overrides dashboard)
                              # Inherits: app error
  settings/
    _layout.tsx               # Defines: errorComponent (overrides app)
    profile.tsx               # Inherits: app loading, settings error
    account.tsx               # Inherits: app loading, settings error

Inheritance Resolution:

| Route | Loading Component | Error Component | |-------|------------------|-----------------| | /dashboard | dashboard/_layout | _app | | /dashboard/users | dashboard/_layout | _app | | /dashboard/analytics | analytics (own) | _app | | /settings/profile | _app | settings/_layout | | /settings/account | _app | settings/_layout |

Benefits

Less Boilerplate - Define defaults once, use everywhere
Consistent UX - All routes in a section share the same loading/error experience
Full Control - Override at any level when you need custom behavior
Type Safe - Full TypeScript support with proper type inference
Zero Runtime Cost - Inheritance resolved at build time


generatePath(path, params)

A utility to construct URLs with type validation. It ensures you don't pass incorrect parameters to your routes.

Parameters:

  • path: The route pattern (e.g., /blog/:id).
  • params: Object containing:
    • Path parameters: Prefixed with $ (e.g., $id, $lang).
    • Query parameters: Standard keys (e.g., search, page).

Example: Type-Safe Navigation

import { generatePath } from 'solid-file-router'
import { useNavigate } from '@solidjs/router'

export function NavigationButton() {
  const navigate = useNavigate()

  const goToPost = (postId: string) => {
    // ✅ Type Safe: TS will error if $id is missing
    const url = generatePath('/blog/:id', {
      $id: postId,      // Path param
      ref: 'newsletter' // Query param -> /blog/123?ref=newsletter
    })

    navigate(url)
  }

  return <button onClick={() => goToPost('123')}>Read Post</button>
}

virtual:routes

The virtual module that exposes the generated routing configuration.

Exports:

  • FileRouter: High-level component to render the app (Easy to use).
  • fileRoutes: The raw RouteDefinition array for @solidjs/router.
  • Root: The component exported from _app.tsx.

Example1: Custom Base URL

import { render } from 'solid-js/web'
import { Router } from '@solidjs/router'
import { FileRouter } from 'virtual:routes'

render(() => <FileRouter base="/app" />, document.getElementById('app')!)

Example2: Custom Router Integration

If you need more control than <FileRouter> provides (e.g., preload or use <HashRouter />), you can use the raw exports:

import { render } from 'solid-js/web'
import { Router } from '@solidjs/router'
import { fileRoutes, Root } from 'virtual:routes'

render(() => (
  <Router
    root={<Root />} // Transformed `src/pages/_app.tsx`
    preload={true}
    {/* Other props */}
  >
    {fileRoutes}
  </Router>
), document.getElementById('app')!)

Type Definition

In tsconfig.json

{
  "compilerOptions": {
    "types": [
      "solid-file-router/client"
    ]
  }
}

Configuration

Options passed to the fileRouter() plugin in vite.config.ts.

interface FileRouterPluginOption {
  /**
   * The output file path where the page types will be saved.
   * @default 'src/routes.d.ts'
   */
  output?: string
  /**
   * The base directory of `src/pages`.
   *
   * e.g. If your `_app.tsx` is located at `packages/app/module/src/pages/_app.tsx`,
   * You need to setup to `packages/app/module/`
   * @default ''
   */
  baseDir?: string
  /**
   * A list of glob patterns to be ignored during processing.
   *
   * Default is {@link DEFAULT_IGNORES}: all files in `components/`, `node_modules/` and `dist/`
   */
  ignore?: string[]
  /**
   * Whether to reload the page when route files change.
   * @default true
   */
  reloadOnChange?: boolean
  /**
   * Route's dts config to control Route's info type
   * @example
   * ```ts
   * {
   *   title: 'string',
   *   description: 'string',
   *   auth: {
   *     required: 'boolean',
   *     code: 'string',
   *   },
   *   tags: 'string[]',
   * }
   * ```
   */
  infoDts?: InfoTypeDefinition
  /**
   * Component inheritance configuration.
   * 
   * Controls how loading and error components are inherited from layouts.
   * 
   * @default { enabled: true, inheritLoading: true, inheritError: true }
   */
  inheritance?: {
    /**
     * Whether to enable component inheritance globally.
     * When false, routes will not inherit loading/error components from layouts.
     * @default true
     */
    enabled?: boolean
    /**
     * Whether to inherit loadingComponent from layouts.
     * Only applies when `enabled` is true.
     * @default true
     */
    inheritLoading?: boolean
    /**
     * Whether to inherit errorComponent from layouts.
     * Only applies when `enabled` is true.
     * @default true
     */
    inheritError?: boolean
  }
}

Configuring Component Inheritance

By default, all routes inherit loading and error components from their layouts. You can control this behavior at both the plugin level (build-time) and route level (runtime).

Build-Time Configuration (Plugin Level)

Control inheritance globally for all routes:

// vite.config.ts
import { fileRouter } from 'solid-file-router/plugin'

export default defineConfig({
  plugins: [
    fileRouter({
      // Disable all inheritance globally
      inheritance: {
        enabled: false
      }
    })
  ]
})

Or selectively disable specific component types:

// vite.config.ts
export default defineConfig({
  plugins: [
    fileRouter({
      inheritance: {
        enabled: true,
        inheritLoading: false,  // Routes won't inherit loading components
        inheritError: true      // Routes will still inherit error components
      }
    })
  ]
})

Runtime Configuration (Route Level)

Control inheritance for individual routes using the inherit property:

// Disable all inheritance for this route
export default createRoute({
  component: () => <SpecialPage />,
  inherit: false  // No loading/error components from layouts
})
// Selectively disable inheritance
export default createRoute({
  component: () => <CustomPage />,
  loadingComponent: () => <CustomLoader />,
  inherit: {
    loading: false,  // Don't inherit loading component
    error: true      // Still inherit error component (default)
  }
})

Configuration Priority

The inheritance resolution follows this priority order:

  1. Route-level inherit configuration (highest priority)

    • inherit: false disables all inheritance
    • inherit: { loading: false } disables loading inheritance
    • inherit: { error: false } disables error inheritance
  2. Build-time plugin configuration

    • inheritance.enabled: false disables globally
    • inheritance.inheritLoading: false disables loading inheritance globally
    • inheritance.inheritError: false disables error inheritance globally
  3. Default behavior (lowest priority)

    • Inheritance enabled for both loading and error components

Use Cases

Performance-Critical Routes:

// Skip wrapper components for maximum performance
export default createRoute({
  component: () => <HighPerformancePage />,
  inherit: false
})

Custom Error Handling:

// Use custom error handling instead of inherited error boundary
export default createRoute({
  component: () => <CustomErrorHandlingPage />,
  errorComponent: (props) => <CustomErrorUI error={props.error} />,
  inherit: { error: false }
})

Gradual Migration:

// During migration, disable inheritance for legacy routes
export default createRoute({
  component: () => <LegacyPage />,
  inherit: false  // Legacy page handles its own loading/error states
})

Credit

Highly inspired by generouted. Created to provide better customization for SolidJS specific features like lazy loading route components while keeping route metadata eager.

License

MIT