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 🙏

© 2026 – Pkg Stats / Ryan Hefner

aaex-file-router

v2.5.0

Published

A file-based routing system for React projects that automatically generates routes from your file structure. Similar to Next.js App Router or Remix file conventions.

Downloads

231

Readme

AAEX File Router

A file-based routing system for React projects that automatically generates routes from your file structure. Similar to Next.js App Router or Remix file conventions.

V. 2.0.4

Added support for root layout and 404 pages

Table of Contents

Features

  • Automatic Route Generation: Routes are generated based on your file and folder structure
  • Layout Support: Create layout.tsx files to wrap nested routes
  • Slug Support: Creates dynamic routes for [slug] files
  • Static & Lazy Loading: Top-level routes use static imports, nested routes use lazy loading
  • Hot Reload: Vite plugin watches for file changes and regenerates routes automatically
  • TypeScript Support: Full TypeScript support with generated route types
  • Type safe link component: Link component that knows what routes are available

Installation

npm install aaex-file-router

Quick Start

1. Create your pages structure

src/pages/
└── dashboard/
    ├── loading.tsx      ← Used as fallback for ALL lazy imports below
    ├── index.tsx
    ├── stats/
    │   ├── loading.tsx  ← Overrides parent
    │   └── weekly.tsx
    └── users/
        └── [id].tsx     ← used for dynamic routes ex :users/123

2. Configure Vite

// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { aaexFileRouter } from "aaex-file-router/plugin";

export default defineConfig({
  plugins: [
    react(),
    aaexFileRouter({
      pagesDir: "./src/pages", //page files location(optional: default ./src/pages)
      outputFile: "./src/routes.ts", //generated routes (default: ./src/routes.ts)
    }),
  ],
});

3. Use in your app

Note: Since v1.4.0 every lazy-loaded route is automatically wrapped in a Suspense boundary, using the nearest loading.tsx file as the fallback.

1. Using createBrowserRouter (recommended for most users)

// src/App.tsx
import "./App.css";
import routes from "./routes";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import { Suspense } from "react";

function App() {
  const router = createBrowserRouter(routes);

  return (
    <Suspense fallback={<div>Loading...</div>}>
      <RouterProvider router={router} />
    </Suspense>
  );
}

export default App;

2. Using nested Route elements

Note I will probably create a custom route provider using this version later since this is the only solution that works with VITE-SSR if you wrap client in <BrowserRouter/> and server in <StaticRouter/>

//src/App.tsx
import {
  BrowserRouter,
  Routes,
  Route,
  type RouteObject,
} from "react-router-dom";
import routes from "./routes";
import { Suspense } from "react";
import "./App.css";

//recursivly creates nested routes
function createRoutes(route: RouteObject) {
  return (
    <Route key={route.path} path={route.path} element={route.element}>
      {route.children?.map((child) => createRoutes(child))}
    </Route>
  );
}

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div>Loading...</div>}>
        <Routes>{routes.map((route) => createRoutes(route))}</Routes>
      </Suspense>
    </BrowserRouter>
  );
}

export default App;

File Conventions

index.tsx

Renders at the parent route path.

pages/index.tsx       → "/"
pages/about/index.tsx → "/about"

layout.tsx

Wraps all sibling and nested routes. Children are rendered in an <Outlet />. Also works for root layout if placed directly in /pages

// pages/admin/layout.tsx
import { Outlet } from "react-router-dom";

export default function AdminLayout() {
  return (
    <div>
      <nav>Admin Navigation</nav>
      <Outlet /> {/* Nested routes render here */}
    </div>
  );
}

404.tsx

Root & folder level fallback. Creating a file named 404.tsx inside the page root or inside a subfolder creates a 404 page for that segment. root => /nonexisting subfolder ex: blog => blog/nonexisting

will render your 404

//src/pages/404.tsx

export default function NotFound() {
  return <>404 not found!</>;
}

Slug files

Filenames wrapper in square brackets [filename] will resolve to a dynamic route

src/pages/test/[<filename>].tsx → "/test/:<filename>"
// src/pages/test/[slug].tsx

import { useParams } from "react-router-dom";

export default function TestWithSlug() {
  // replace slug with what the file is called
  const { slug } = useParams();

  return <div>{slug}</div>;
}

Named files

Any other .tsx file becomes a route based on its filename.

pages/about.tsx       → "/about"
pages/blog/post.tsx   → "/blog/post"

Route Resolution Examples

| File Structure | Route Path | | --------------------------------------- | -------------------- | | src/pages/index.tsx | / | | src/pages/about.tsx | /about | | src/pages/blog/index.tsx | /blog | | src/pages/blog/post.tsx | /blog/post | | src/pages/admin/layout.tsx + children | /admin/* (grouped) |

Layouts

Layouts wrap their child routes and provide shared UI:

// src/pages/dashboard/layout.tsx
import { Outlet } from "react-router-dom";

export default function DashboardLayout() {
  return (
    <div className="dashboard">
      <Sidebar />
      <main>
        <Outlet />
      </main>
    </div>
  );
}

All routes in src/pages/dashboard/* will render inside this layout.

Import Strategy

| File type | | | Import style | | | ------------------------------------- | --- | --- | ------------- | --- | | pages/*.tsx (top level) | | | static import | | | Files inside a folder with layout.tsx | | | Lazy loaded | | | Files inside a folder without layout | | | Lazy loaded | | | layout.tsx | | | Static import | | loading.tsx | | | Static import |


FileLink component

The FileLink component is a type safe wrapper for the Link component in react router that uses an autogenerated type to check which routes are available.

Notice!

At the moment it can only do the basic routing where the "to" prop is a string. React Router's normal Link still works in cases where type safety is less important.

Usage

If reades the type file that is automatically generated

users/{string} is what users/:slug gets translated to this means users/ allows any string after even if the route dosnt exist. Will look into better solution

// src/routeTypes.ts
// * AUTO GENERATED: DO NOT EDIT
/
export type FileRoutes = "/" | "test" | "users/{string}";
// src/pages/index.tsx
import { FileLink } from "aaex-file-router";
import type { FileRoutes } from "../routeTypes"; //import type

export default function Home() {
  return (
    <>
      Hello Home!
      <FileLink<FileRoutes> to="test">Test safe</FileLink>
      {/* or without type safety */}
      <FileLink to="some-route">Non safe</FileLink>
    </>
  );
}

useScroll

Custom React hook that scrolls to the top after navigation. Designed to work seamlessly with client-side routing while remaining SSR-safe.

Features

  • Automatically scrolls on route change

  • Supports smooth scrolling

  • Can scroll either the window or a specific container

  • Safe to use in SSR environments

Usage

import { useScroll } from "aaex-file-router";

export default function PageWithNavigation() {
  useScroll();

  return <>{/* rest of content */}</>;
}

Options

useScroll accepts an optional config object.

useScroll({
  behavior?: ScrollBehavior;
  container?: HTMLElement | null;
});

| Option | Type | Default | Description | | ----------- | --------------------- | ---------- | ---------------------------------------- | ------------------------- | | behavior | "auto" | "smooth" | "auto" | Scroll animation behavior | | container | HTMLElement \| null | null | Scrolls this element instead of window |

Example: smooth scroll

useScroll({
  behavior: "smooth",
});

Example: scroll a container

const ref = useRef<HTMLDivElement>(null);

useScroll({
  container: ref.current,
});

return <div ref={ref}>...</div>;

Generated files

routes.ts

Generated route definition file

// src/routes.ts
...imports
export default routes = [
    {
        path: "/",
        element: React.createElement(Index)},
    {
        path: "test",
        element: React.createElement(TestLayout),
        children:
        [
            {
            path: "",
            element: React.createElement(
                React.Suspense,
                { fallback: React.createElement(TestLoading) },
                React.createElement(React.lazy(() => import("./pages/test/index.tsx")))
            )
            },
            ...
        ]
    }
    ]

routeTypes.ts

Exports TypeScript union type of existing routes

export type FileRoutes = `/` | `test`;

API Reference

FileScanner

Scans the file system and converts files into a structured format.

import { FileScanner } from "aaex-file-router/core";

const scanner = new FileScanner("./src/pages");
const fileData = await scanner.get_file_data();

RouteGenerator

Converts file structure into React Router route configuration.

import { RouteGenerator } from "aaex-file-router/core";

const generator = new RouteGenerator();
const routesCode = await generator.generateRoutesFile(fileData);

aaexFileRouter (Vite Plugin)

Automatically watches for file changes and regenerates routes.

import { aaexFileRouter } from "aaex-file-router/plugin";

export default defineConfig({
  plugins: [
    aaexFileRouter({
      pagesDir: "./src/pages",
      outputFile: "./src/routes.ts",
    }),
  ],
});

Server routing

If you are using vite SSR you want to configure your vite config with the serverRouter plugin instead of the normal one.

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { aaexServerRouter } from "aaex-file-router/plugin";

// https://vite.dev/config/
export default defineConfig({
  plugins: [react(), aaexServerRouter()],
});

this plugin generates 2 route files

1. server-routes.ts

Includes absolute path to the file modulePath

route example:

{
    "path": "",
    "element": React.createElement(Index),
    "modulePath": "C:/Users/tmraa/OneDrive/Dokument/AaExJS-documentation/test-app/src/pages/index.tsx"
  },

2. client-routes.ts

Just the normal route file.

How It Works

  1. File Scanning: Recursively scans your pages directory and builds a file tree
  2. Route Generation: Converts the file structure into React Router RouteObject format
  3. Smart Importing:
    • Top-level files use static imports for faster initial load
    • Nested/grouped routes use lazy loading for code splitting
    • Layout files are statically imported as route wrappers
  4. Auto-Regeneration: Vite plugin watches for changes and automatically regenerates routes.ts

Performance Considerations

  • Static Imports: Top-level routes are statically imported, included in the main bundle
  • Code Splitting: Routes nested in layout groups are lazy-loaded, improving initial bundle size
  • Watch Mode: File watching only runs in development (vite serve), not in production builds

Common Patterns

Shared Layout

pages/
├── layout.tsx         # Wraps entire app
├── index.tsx
└── about.tsx

Nested Layouts

pages/
├── layout.tsx                 # Root layout
├── admin/
│   ├── layout.tsx            # Admin layout (inherits from root)
│   ├── index.tsx
│   └── users.tsx

Route Groups Without Layout

pages/
├── blog/
│   ├── post.tsx              # Routes as /blog/post (no grouping)
│   └── author.tsx            # Routes as /blog/author

Troubleshooting

Routes not updating on file change

  • Ensure Vite dev server is running (npm run dev)
  • Check that pagesDir in vite config matches your actual pages directory

Duplicate imports in generated file

  • This shouldn't happen, but if it does, try restarting the dev server
  • Check for files with the same name in different directories

Unexpected route paths

  • Remember: index.tsx files inherit their parent's path
  • Directories without layout.tsx flatten their children into absolute routes
  • File names are converted to lowercase for routes

License

MIT