Server-side rendering
In server-side rendering, Reactive Route acts as a matcher that executes the lifecycle, redirect chains, and validation of incoming data. For this, it is enough to call router.init and handle redirects.
To build a more complete picture, let us use a full server with Express.js.
Server
import { renderToString } from 'react-dom/server';
import { getRouter, RouterContext } from 'router';
import fs from 'node:fs';
import express from 'express';
import { RedirectError } from 'reactive-route';
import { App } from 'components/App';
const app = express();
app.get('*', async (req, res) => {
const template = `
<html>
<body><div id="app"><!-- HTML --></div></body>
</html>`;
const router = getRouter();
try {
await router.init(req.originalUrl);
} catch (error: unknown) {
if (error instanceof RedirectError) {
console.log(`Some beforeEnter redirected to ${error.message}`);
return res.redirect(error.message);
}
return res.status(500).send('Unexpected error');
}
const html = renderToString(
<RouterContext.Provider value={{ router }}>
<App />
</RouterContext.Provider>
);
res.send(template.replace(`<!-- HTML -->`, html));
});import { renderToString } from 'preact-render-to-string';
import { getRouter, RouterContext } from 'router';
import fs from 'node:fs';
import express from 'express';
import { RedirectError } from 'reactive-route';
import { App } from 'components/App';
const app = express();
app.get('*', async (req, res) => {
const template = `
<html>
<body><div id="app"><!-- HTML --></div></body>
</html>`;
const router = getRouter();
try {
await router.init(req.originalUrl);
} catch (error: unknown) {
if (error instanceof RedirectError) {
console.log(`Some beforeEnter redirected to ${error.message}`);
return res.redirect(error.message);
}
return res.status(500).send('Unexpected error');
}
const html = renderToString(
<RouterContext.Provider value={{ router }}>
<App />
</RouterContext.Provider>
);
res.send(template.replace(`<!-- HTML -->`, html));
});import { generateHydrationScript, renderToString } from 'solid-js/web';
import { getRouter, RouterContext } from 'router';
import fs from 'node:fs';
import express from 'express';
import { RedirectError } from 'reactive-route';
import { App } from 'components/App';
const app = express();
app.get('*', async (req, res) => {
const template = `
<html>
<body><div id="app"><!-- HTML --></div></body>
<!-- HYDRATION -->
</html>`;
const router = getRouter();
try {
await router.init(req.originalUrl);
} catch (error: unknown) {
if (error instanceof RedirectError) {
console.log(`Some beforeEnter redirected to ${error.message}`);
return res.redirect(error.message);
}
return res.status(500).send('Unexpected error');
}
const html = renderToString(() => (
<RouterContext.Provider value={{ router }}>
<App />
</RouterContext.Provider>
));
res.send(template
.replace(`<!-- HTML -->`, html)
.replace(`<!-- HYDRATION -->`, generateHydrationScript())
);
});import { createSSRApp } from 'vue';
import { renderToString } from 'vue/server-renderer';
import { getRouter, routerStoreKey } from './router';
import fs from 'node:fs';
import express from 'express';
import { RedirectError } from 'reactive-route';
import { App } from 'components/App';
const app = express();
app.get('*', async (req, res) => {
const template = `
<html>
<body><div id="app"><!-- HTML --></div></body>
</html>`;
const router = getRouter();
try {
await router.init(req.originalUrl);
} catch (error: unknown) {
if (error instanceof RedirectError) {
console.log(`Some beforeEnter redirected to ${error.message}`);
return res.redirect(error.message);
}
return res.status(500).send('Unexpected error');
}
const html = await renderToString(
createSSRApp(App, { router }).provide(routerStoreKey, { router })
);
res.send(template.replace(`<!-- HTML -->`, html));
});In this example, links to js and css files are not inserted into the final HTML; this is usually done by the bundler. The full code with esbuild setup can be seen in Examples.
Client
On the client side, only skipLifecycle: true is added, because the lifecycle has already been called on the server, and the corresponding UI framework hydrate methods are used.
import { hydrateRoot } from 'react';
import { App } from './App';
import { getRouter, RouterContext } from './router';
const router = getRouter();
await router.init(location.href, {
skipLifecycle: true
});
hydrateRoot(
document.getElementById('app')!,
<RouterContext.Provider value={{ router }}>
<App />
</RouterContext.Provider>
);import { hydrate } from 'preact';
import { App } from './App';
import { getRouter, RouterContext } from './router';
const router = getRouter();
await router.init(location.href, {
skipLifecycle: true
});
hydrate(
<RouterContext.Provider value={{ router }}>
<App />
</RouterContext.Provider>,
document.getElementById('app')!
);import { hydrate } from 'solid-js/web';
import { App } from './App';
import { getRouter, RouterContext } from './router';
const router = getRouter();
await router.init(location.href, {
skipLifecycle: true
});
hydrate(
() => (
<RouterContext.Provider value={{ router }}>
<App />
</RouterContext.Provider>
),
document.getElementById('app')!
);import { createSSRApp } from 'vue';
import { App } from './App';
import { getRouter, routerStoreKey } from './router';
const router = getRouter();
await router.init(location.href, {
skipLifecycle: true
});
createSSRApp(App, { router })
.provide(routerStoreKey, { router })
.mount('#app');MPA
The Multi Page Application mode works out of the box if the Link component was created following the documentation example (that is, it uses the native href). When navigating to new pages, the server will return ready-made HTML, and the application remains functional even with JavaScript disabled in the browser.
Thus, the set of Config here acts as a route description with validation and, if necessary, an async lifecycle for loading data, and in some cases can replace traditionally used server routing libraries.