A fully automatic, Next.js App Router-style file-based routing solution for React — without using Next.js.
Supports:
- File-based routing
- Nested layouts
- Route grouping
- Dynamic routes
- Error boundary
- 404 page
- Loader support for data fetching
Built for React Router DOM + Vite — simple, fast, familiar.
✅ Next.js App Router-like routing in React apps
✅ Auto-load pages from the /app folder
✅ Support for Layouts via layout.jsx
✅ Route Groups with (group) folders
✅ Dynamic routes with [slug], [...slug], [[slug]]
✅ Error boundaries via error.jsx
✅ 404 Not Found handling with 404.jsx
✅ Loader support for data fetching loader.jsx
✅ Fully type-safe (TypeScript supported)
Try it on StackBlitz:
npm install react-next-routersrc/
 └── app/
      ├── layout.jsx          # Root layout
      ├── page.jsx            # Index route ('/')
      ├── about/
      │    └── page.jsx       # '/about'
      ├── blog/
      │    ├── [slug]/
      │    │     ├── page.jsx   # '/blog/:slug'
      │    │     └── loader.jsx  # Loader for data fetching
      │    └── layout.jsx     # Layout for '/blog/*'
      ├── (admin)/
      │    ├── dashboard/
      │    │      └── page.jsx # '/dashboard'
      │    └── layout.jsx     # Layout for group
      ├── error.jsx           # Error boundary
      ├── 404.jsx             # Not Found page
      ├── loading.jsx         # Loading component (renders while loading)
You can
loader.jsxalongside anypage.jsxto fetch data before rendering the page.
Add aapp/loading.jsxfile to show a loading UI while the loader is running.
Example src/app/page.jsx:
export default function Home({ data }) {
  return <h1>Home Page {data && <span>{data.message}</span>}</h1>;
}Example src/app/layout.jsx:
export default function RootLayout({ children }) {
  return (
    <div>
      <header>Header Content</header>
      <main>{children}</main>
    </div>
  );
}Example src/app/loader.jsx:
// This loader runs before the sibling page.jsx and its return value is passed as the 'data' prop
export default async function loader() {
  // You can fetch from any API or return any data
  const res = await fetch("https://api.example.com/message");
  const data = await res.json();
  return { message: data.message };
}Example src/App.jsx:
import { AppRouter } from "react-next-router";
function App() {
  return <AppRouter />;
}
export default App;| File | URL Pattern | 
|---|---|
| app/page.jsx | / | 
| app/about/page.jsx | /about | 
| app/blog/[slug]/page.jsx | /blog/:slug | 
| app/blog/[...slug]/page.jsx | /blog/*(catch-all) | 
| app/blog/[[slug]]/page.jsx | /blog(optional param) | 
| app/blog/[slug]/loader.jsx | Data loader for /blog/:slug | 
| app/loading.jsx | Loading UI while data is fetched | 
You can now use the useAppRouter() hook to get a JSON structure of all matched routes. This is useful when you want to inspect or manipulate the route config manually — for example, inside a custom RouterProvider or createBrowserRouter setup.
import { useAppRouter } from "react-next-router";
const MyComponent = () => {
  const router = useAppRouter();
  console.log(router);
  return <div>Check the console for the matched routes!</div>;
};The useNextParams hook lets you easily access dynamic route parameters (like [slug], [...slug]) in your components. recommended for use in pages that have dynamic segments instead of using useParams from React Router.
import { useNextParams } from "react-next-router";
export default function BlogPost() {
  const params = useNextParams();
  // For a route like /blog/hello, params.slug === 'hello'
  // For a catch-all route like /blog/a/b, params.slug === ['a', 'b']
  return <div>Slug: {JSON.stringify(params.slug)}</div>;
}Folders wrapped in parentheses will not affect the URL path:
app/
 ├── (auth)/
 │     └── login/page.jsx  # '/login'
 └── (dashboard)/
       └── home/page.jsx   # '/home'
Every folder can contain its own layout.jsx which wraps all its subroutes:
app/
 ├── layout.jsx        # Root layout
 └── blog/
       ├── layout.jsx  # Blog section layout
       └── [slug]/
             └── page.jsx
Define a 404.jsx file to catch all unmatched routes:
app/
 └── 404.jsx
Add an error.jsx file to handle route-specific errors:
app/
 └── error.jsx
Add a loader.jsx file alongside any page.jsx to fetch data before rendering the page. The returned value will be passed as the data prop to the sibling page.jsx component.
Example:
app/
 └── about/
       ├── page.jsx
       └── loader.jsx
MIT
Inspired by Next.js App Router and built with React Router DOM + Vite ❤️