Core Router
@spa-tools/core-router simplifies modern-day SPA routing, shedding all excess baggage without compromising functionality.
Dev-Defined Route Shape
Dead-Simple Navigation
Absolute Flow Control
Succinct Options
React (or not)
TypeScript First
Zero Dependencies
6 kB (index.cjs)
Tree-shakable
Intent
The Core Router is intended for real-world applications that neither require nor desire SEO optimization. If your app depends heavily on SEO, then you should consider using a SSG/SSR solution that takes care of routing+SEO (e.g. Next, Gatsby, Nuxt, Hugo, Docusaurus, etc.)
Installation
To install Core Router in your project, use your favorite package manager and run the respective command:
- npm
- Yarn
- pnpm
npm install @spa-tools/core-router
yarn add @spa-tools/core-router
pnpm add @spa-tools/core-router
Quickstart
Choose your preferred implementation:
- +
- +
The first thing to do is create/define your routes.
import { CoreRoute, routesFactory } from '@spa-tools/core-router';
// adding custom properties to your routes is as simple
// as creating an interface that extends CoreRoute
interface MyCustomRoute extends CoreRoute {
requiresAuth: boolean;
}
// to create your routes, first generate a route factory function
const createMyRoutes = routesFactory<MyCustomRoute>();
// next define and create all of your routes, which you'll
// typically export to use throughout your app
export const myRoutes = createMyRoutes({
dashboardRoute: {
path: '/',
requiresAuth: true,
},
financialsRoute: {
path: '/financials',
requiresAuth: true,
},
loginRoute: {
path: '/login',
requiresAuth: false,
},
signupRoute: {
path: '/signup',
requiresAuth: false,
},
});
Once you've created your routes, next you initialize your router.
import { CoreRouter } from '@spa-tools/core-router';
import { myRoutes } from '..';
import { checkUserAuthentication } from '..';
export const myRouter = CoreRouter.initialize(myRoutes, {
//
// onRouteRequest is the callback you use to perform all
// of your flow control logic in one centalized location
//
// the controller scenarios are only limited by your
// imagination, but ultimately this is where you determine
// if the user's route request should be allowed to proceed
// or if it should be denied or perhaps even redirected.
//
onRouteRequest: async ({ newRoute, newState }) => {
// here we use our custom requiresAuth property to check
// if the user must first be authenticated before allowing
// navigation to the requested route
if (newRoute.requiresAuth) {
// here we call a so-called async function to determine
// if the user is authenticated
const isUserAuthenticated = await checkUserAuthentication();
if (!isUserAuthenticated) {
// since the user is NOT authenticated, we request a
// redirect to the login route by returning a tuple with
// respective route along with state we want to pass along
return [
myRoutes.loginRoute,
{ fromRoute: newRoute, fromState: newState }
]
}
}
// here we ALLOW the route request by returning true; we also
// could've done nothing and by default the route request would
// be allowed to proceed
return true;
}
});
Now you can use your router from anywhere in your app to navigate to any route.
import { myRouter, myRoutes } from '..';
function navigateToFinanicals() {
myRouter.navigate(myRoutes.financialsRoute);
}
The first thing to do is create/define your routes.
import { routesFactory } from '@spa-tools/core-router';
// to create your routes, first generate a route factory function
const createMyRoutes = routesFactory();
// next define and create all of your routes, which you'll
// typically export to use throughout your app
export const myRoutes = createMyRoutes({
dashboardRoute: {
path: '/',
requiresAuth: true,
},
financialsRoute: {
path: '/financials',
requiresAuth: true,
},
loginRoute: {
path: '/login',
requiresAuth: false,
},
signupRoute: {
path: '/signup',
requiresAuth: false,
},
});
Once you've created your routes, next you initialize your router.
import { CoreRouter } from '@spa-tools/core-router';
import { myRoutes } from '..';
import { checkUserAuthentication } from '..';
export const myRouter = CoreRouter.initialize(myRoutes, {
//
// onRouteRequest is the callback you use to perform all
// of your flow control logic in one centalized location
//
// the controller scenarios are only limited by your
// imagination, but ultimately this is where you determine
// if the user's route request should be allowed to proceed
// or if it should be denied or perhaps even redirected.
//
onRouteRequest: async ({ newRoute, newState }) => {
// here we use our custom requiresAuth property to check
// if the user must first be authenticated before allowing
// navigation to the requested route
if (newRoute.requiresAuth) {
// here we call a so-called async function to determine
// if the user is authenticated
const isUserAuthenticated = await checkUserAuthentication();
if (!isUserAuthenticated) {
// since the user is NOT authenticated, we request a
// redirect to the login route by returning a tuple with
// respective route along with state we want to pass along
return [
myRoutes.loginRoute,
{ fromRoute: newRoute, fromState: newState }
]
}
}
// here we ALLOW the route request by returning true; we also
// could've done nothing and by default the route request would
// be allowed to proceed
return true;
}
});
Now you can use your router from anywhere in your app to navigate to any route.
import { myRouter, myRoutes } from '..';
function navigateToFinanicals() {
myRouter.navigate(myRoutes.financialsRoute);
}
The first thing to do is create/define your routes.
import { CoreReactRoute, reactRoutesFactory } from '@spa-tools/core-router';
// adding custom properties to your React routes is as simple
// as creating an interface that extends CoreReactRoute
interface MyCustomRoute extends CoreReactRoute {
requiresAuth: boolean;
}
// to create your routes, first generate a route factory function
const createMyRoutes = reactRoutesFactory<MyCustomRoute>();
// next define and create all of your routes, which you'll
// typically export to use throughout your app; note the use
// of JSX to define the component to render for each route
export const myRoutes = createMyRoutes({
dashboardRoute: {
component: <DashboardView />,
path: '/',
requiresAuth: true,
},
financialsRoute: {
component: <FinancialsView />,
path: '/financials',
requiresAuth: true,
},
loginRoute: {
component: <LoginView />,
path: '/login',
requiresAuth: false,
},
signupRoute: {
component: <SignupView />,
path: '/signup',
requiresAuth: false,
},
});
Once you've created your routes, next you create your router.
import { CoreReactRouter, OnCoreReactRouteRequest } from '@spa-tools/core-router';
import { myRoutes } from '..';
import { checkUserAuthentication } from '..';
export function MyRouter() {
//
// onRouteRequest prop of the CoreReactRouter component is the
// callback you use to perform all of your flow control logic
// in one centalized location
//
// the controller scenarios are only limited by your
// imagination, but ultimately this is where you determine
// if the user's route request should be allowed to proceed
// or if it should be denied or perhaps even redirected.
//
const handleRouteRequest: OnCoreReactRouteRequest = async ({ newRoute, newState }) => {
// here we use our custom requiresAuth property to check
// if the user must first be authenticated before allowing
// navigation to the requested route
if (newRoute.requiresAuth) {
// here we call a so-called async function to determine
// if the user is authenticated
const isUserAuthenticated = await checkUserAuthentication();
if (!isUserAuthenticated) {
// since the user is NOT authenticated, we request a
// redirect to the login route by returning a tuple with
// respective route along with state we want to pass along
return [
myRoutes.loginRoute,
{ fromRoute: newRoute, fromState: newState }
]
}
}
// here we ALLOW the route request by returning true; we also
// could've done nothing and by default the route request would
// be allowed to proceed
return true;
};
return (
// here we return a CoreReactRouter component
<CoreReactRouter
// here we assign our route request handler
onRouteRequest={handleRouteRequest}
// here we assign our routes
routes={myRoutes}
/>
);
}
Finally, add your new router as a child to the root of your app tree. Of course we could also just use the CoreReactRouter component here directly instead of using a router wrapper component.
import { StrictMode } from 'react';
import ReactDOM from 'react-dom/client';
import { MyRouter } from '..';
ReactDOM.createRoot(document.getElementById('root')!).render(
<StrictMode>
<MyRouter />
</StrictMode>
);
Now you can navigate to any route from any function component in your app with the useCoreRouter hook.
import { useCoreRouter } from '@spa-tools/core-router';
import { myRoutes } from '..';
function MyFinancialCta() {
const { navigate } = useCoreRouter();
return (
<button onClick={() => navigate(myRoutes.financialsRoute)}>
Go to Financials
</button>
);
}
Or you can use the NavLink component.
import { NavLink } from '@spa-tools/core-router';
import { myRoutes } from '..';
function MyLeftNavComponent() {
return (
<ul>
<li>
<NavLink route={myRoutes.financialsRoute}>Financials</NavLink>
</li>
<li>
<NavLink route={myRoutes.dashboardRoute}>Dashboard</NavLink>
</li>
</ul>
);
}
The first thing to do is create/define your routes.
import { reactRoutesFactory } from '@spa-tools/core-router';
// to create your routes, first generate a route factory function
const createMyRoutes = reactRoutesFactory();
// next define and create all of your routes, which you'll
// typically export to use throughout your app; note the use
// of JSX to define the component to render for each route
export const myRoutes = createMyRoutes({
dashboardRoute: {
component: <DashboardView />,
path: '/',
requiresAuth: true,
},
financialsRoute: {
component: <FinancialsView />,
path: '/financials',
requiresAuth: true,
},
loginRoute: {
component: <LoginView />,
path: '/login',
requiresAuth: false,
},
signupRoute: {
component: <SignupView />,
path: '/signup',
requiresAuth: false,
},
});
Once you've created your routes, next you create your router.
import { CoreReactRouter } from '@spa-tools/core-router';
import { myRoutes } from '..';
import { checkUserAuthentication } from '..';
export function MyRouter() {
//
// onRouteRequest prop of the CoreReactRouter component is the
// callback you use to perform all of your flow control logic
// in one centalized location
//
// the controller scenarios are only limited by your
// imagination, but ultimately this is where you determine
// if the user's route request should be allowed to proceed
// or if it should be denied or perhaps even redirected.
//
const handleRouteRequest = async ({ newRoute, newState }) => {
// here we use our custom requiresAuth property to check
// if the user must first be authenticated before allowing
// navigation to the requested route
if (newRoute.requiresAuth) {
// here we call a so-called async function to determine
// if the user is authenticated
const isUserAuthenticated = await checkUserAuthentication();
if (!isUserAuthenticated) {
// since the user is NOT authenticated, we request a
// redirect to the login route by returning a tuple with
// respective route along with state we want to pass along
return [
myRoutes.loginRoute,
{ fromRoute: newRoute, fromState: newState }
]
}
}
// here we ALLOW the route request by returning true; we also
// could've done nothing and by default the route request would
// be allowed to proceed
return true;
};
return (
// here we return a CoreReactRouter component
<CoreReactRouter
// here we assign our route request handler
onRouteRequest={handleRouteRequest}
// here we assign our routes
routes={myRoutes}
/>
);
}
Finally, add your new router as a child to the root of your app tree. Of course we could also just use the CoreReactRouter component here directly instead of using a router wrapper component.
import { StrictMode } from 'react';
import ReactDOM from 'react-dom/client';
import { MyRouter } from '..';
ReactDOM.createRoot(document.getElementById('root')).render(
<StrictMode>
<MyRouter />
</StrictMode>
);
Now you can navigate to any route from any function component in your app with the useCoreRouter hook.
import { useCoreRouter } from '@spa-tools/core-router';
import { myRoutes } from '..';
function MyFinancialCta() {
const { navigate } = useCoreRouter();
return (
<button onClick={() => navigate(myRoutes.financialsRoute)}>
Go to Financials
</button>
);
}
Or you can use the NavLink component.
import { NavLink } from '@spa-tools/core-router';
import { myRoutes } from '..';
function MyLeftNavComponent() {
return (
<ul>
<li>
<NavLink route={myRoutes.financialsRoute}>Financials</NavLink>
</li>
<li>
<NavLink route={myRoutes.dashboardRoute}>Dashboard</NavLink>
</li>
</ul>
);
}