-
|
I'm looking on how to work with Tanstack Router/Tanstack Start where some routes are controlled entirely in a database or other system. Examples:
Requirements:
I am able to achieve this with Next.js using Proxy/Middleware + Any help & guidance would be appreciated. Thank you! |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 2 replies
-
what does this mean? after SSR, everything happens on the client (unless you call a server function or similar) |
Beta Was this translation helpful? Give feedback.
-
|
Okay I have something that seems to work well using a Splat Route. Approach:
The isomorphic component works as follows using
Attributes:
import { createFileRoute, LoaderFnContext, notFound } from '@tanstack/react-router'
import { createIsomorphicFn, createServerFn } from '@tanstack/react-start';
import { use } from 'react';
import z from 'zod';
// --------------------------------------------------------
function createSplatComponentRegistry<
R extends Record<string, () => Promise<React.ComponentType<any>>>,
>(registry: R) {
const cache = {
async: new Map<string, Promise<React.ComponentType<any>>>(),
sync: new Map<string, React.ComponentType<any>>(),
};
/** Imports the item from the registry and caches its promise and promise output */
const load = (key: string) => {
if (cache.async.has(key)) {
return cache.async.get(key)!;
}
const promise = registry[key]();
cache.async.set(key, promise);
promise.then((component) => {
cache.sync.set(key, component);
});
return promise;
}
/** Return the loaded item from the cache */
const loadSync = (key: string) => {
if (cache.sync.has(key)) {
return cache.sync.get(key)!;
}
throw new Error(`Component '${key}' not found in sync cache`);
}
/** An isomorphic component cabale of synchronous server rendering and asynchronous client rendering */
const component = createIsomorphicFn()
.server((key: string) => {
// server should preload component during loader so we can do instant SSR without suspense
return loadSync(key as string);
})
.client((key: string) => {
return (props: any) => {
// client should suspend until the component is loaded, allowing us to pull in the component's bundle
const Component = use(load(key as string));
return <Component {...props} />;
}
})
return {
load,
loadSync,
component,
}
}
// --------------------------------------------------------
const SplatComponentRegistry = createSplatComponentRegistry({
// simply `export default function SomeComponent(props) { ... }`
BlogPost: () => import('~/components/dynamic-routing/templates/BlogPost').then((module) => module.default),
NewsArticle: () => import('~/components/dynamic-routing/templates/NewsArticle').then((module) => module.default),
});
const sampleRouteDatabase = {
'/some-blog/blog-post-1': {
componentKey: 'BlogPost',
data: {
id: '1',
}
},
'/some-blog/blog-post-2': {
componentKey: 'BlogPost',
data: {
id: '2',
}
},
'/news-article-1': {
componentKey: 'NewsArticle',
data: {
id: '3',
}
},
}
const lookupRoute = createServerFn({ })
.inputValidator(z.object({
path: z.string(),
}))
.handler(async (input) => {
const path = input.data.path;
if (path in sampleRouteDatabase) {
return sampleRouteDatabase[path as keyof typeof sampleRouteDatabase];
}
return null;
})
export const Route = createFileRoute('/dynamic-routing/4/$')({
component: RouteComponent,
loader: async (ctx) => {
const route = await lookupRoute({
data: {
path: '/' + (ctx.params._splat || ''),
},
});
if (!route) {
throw notFound();
}
// import the component
await SplatComponentRegistry.load(route.componentKey);
return {
splatRoute: {
componentKey: route.componentKey,
props: {
...route.data,
},
},
}
}
});
function RouteComponent() {
const loaderData = Route.useLoaderData();
const {
splatRoute,
} = loaderData;
const SplatComponent = SplatComponentRegistry.component(splatRoute.componentKey);
return (
<SplatComponent {...splatRoute.props} />
)
}Some next steps & other ideas I have:
|
Beta Was this translation helpful? Give feedback.
Okay I have something that seems to work well using a Splat Route.
Approach:
throw notFound()and show the usual 404 page.The isomorphic component works as follows using
createIsomorphicFn():