@vavra7/router
v2.2.0
Published
Asynchronous react router supporting a server side rendering. Built to leverage routs based code splitting and displays loading status when chunk of code fetching from server.
Downloads
10
Readme
Async React router supporting SSR
Asynchronous react router supporting a server side rendering. Built to leverage routs based code splitting and displays loading status when chunk of code fetching from server.
Preconditions
Set up react application
Hooks
useLocation()
- returns object of current router locationuseNavigate()
- returns function allowing navigate to another locationuseLocationLoading()
- In case of code splitting and async loading of route. Returns status (boolean) that app is loading chunk of code needed to go to target route.
Components
Link
- renders a tag to given routeOutlet
- renders view
import { Link, Outlet } from '@vavra7/router';
<Link to={{ name: RouteNameEnum.PostDetail, params: { postId: 'a' } }}>post a</Link>
<Outlet />
<Outlet name="widget">
Example of usage
routes definition
Children always means nested route.
import type { RouteConfig } from '@vavra7/router';
import { RouteNameEnum } from '../../../enums/routeName.enum';
import HomeView from '../../../views/home.view';
import NotFoundView from '../../../views/notFound.view';
import Widget1View from '../../../views/posts/widget1.view';
import RootView from '../../../views/root.view';
export const routesConfigs: RouteConfig<RouteNameEnum>[] = [
{
name: RouteNameEnum.Root,
path: '/:lang(en|cs)?',
view: {
component: RootView
},
children: [
{
name: RouteNameEnum.Home,
path: '/',
view: [
{
component: HomeView
},
{
outletName: 'widget',
component: Widget1View
}
]
},
{
name: RouteNameEnum.Posts,
path: '/posts',
view: [
{
loadComponentFce: () =>
import('../../../views/posts.view' /* webpackChunkName: "postsView" */)
},
{
outletName: 'widget',
component: Widget1View
}
],
children: [
{
name: RouteNameEnum.PostDetail,
path: '/:postId',
view: {
loadComponentFce: () =>
import(
'../../../views/posts/postDetail.view' /* webpackChunkName: "postDetailView" */
)
}
}
]
},
{
name: RouteNameEnum.Profile,
path: '/profile',
view: [
{
loadComponentFce: () =>
import('../../../views/profile.view' /* webpackChunkName: "profileView" */)
},
{
outletName: 'widget',
loadComponentFce: () =>
import('../../../views/posts/widget2.view' /* webpackChunkName: "widget2View" */)
}
],
ssr: false,
beforeEnter: async (fromLocation, params) => {
const auth = true;
if (!auth) return { name: RouteNameEnum.Home };
}
}
]
},
{
name: RouteNameEnum.NotFound,
path: '/(.*)',
ssr: false,
view: { component: NotFoundView }
}
];
How to define path
Following rules from this package: https://www.npmjs.com/package/path-to-regexp
Router set up
import type { LocationState } from '@vavra7/router';
import { RouterClient } from '@vavra7/router';
import type { RouteNameEnum } from '../../enums/routeName.enum';
import { routesConfigs } from './routes';
export class Router {
private client?: RouterClient<RouteNameEnum>;
public getClient(): RouterClient<RouteNameEnum> {
if (this.client) {
return this.client;
} else {
return this.initClient();
}
}
private initClient(): RouterClient<RouteNameEnum> {
this.client = new RouterClient<RouteNameEnum>({
debug: process.env.NODE_ENV === 'development',
routesConfigs,
defaultParamsFce: prevParams => ({ lang: prevParams.lang })
});
this.client.locationStore.subscribe(this.onStateChange);
return this.client;
}
private onStateChange(routerState: LocationState): void {
console.log('route has changed')
}
}
export const router = new Router();
Server side
app.use('*', async (req, res) => {
const routerClient = router.getClient();
const { ssr } = await routerClient.navigate(req.originalUrl);
let stringApp = '';
if (ssr) {
stringApp = renderToString(
<RouterProvider client={routerClient}>
<Root />
</RouterProvider>
);
}
const markup = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="main.js" defer></script>
<title>Document</title>
</head>
<body>
<div id="root-slot">${stringApp}</div>
</body>
</html>
`;
res.setHeader('Content-Type', 'text/html');
res.end(markup);
});
Client side
import { RouterProvider } from '@vavra7/router';
import React from 'react';
import { createRoot, hydrateRoot } from 'react-dom/client';
import { router } from './lib/router';
import Root from './root';
async function main(): Promise<void> {
const routerClient = router.getClient();
const { ssr } = await routerClient.navigate(location.pathname + location.search);
const container = document.getElementById('root-slot');
const app = (
<RouterProvider client={routerClient}>
<Root />
</RouterProvider>
);
if (ssr) {
hydrateRoot(container!, app);
} else {
createRoot(container!).render(app);
}
}
main();