S
SolidJS•2mo ago
Folco

Stream SSR & BFF with full SPA transitions

Hey! Question about implementing :solid: SSR & BFF with full SPA transitions. Pls, help 🙂 Have a classic BFF with file-based routing on Fastify. The endpoints call several APIs, gather the data, and save it in fastify.state. After the route handler finishes, a onSendAsyncHookHandler is invoked, which passes to renderToStream a bundle of links to assets (collected from the Vite manifest), initialState, and a few other parameters. And then returns a stream to the client:
const stream = render({
...hydrateAssets,
url: request.raw.url ?? '/',
initialState: request.server.state,
production: isProduction
});
await pipeline(stream, reply.raw);
return stream;
const stream = render({
...hydrateAssets,
url: request.raw.url ?? '/',
initialState: request.server.state,
production: isProduction
});
await pipeline(stream, reply.raw);
return stream;
In the App component, I render the entire HTML document. In the <head> I add <link> tags for the assets and a <script> pointing to the client entry for hydration. I store the initialState in a custom StoreProvider. I’m using a Router wrapped in Suspense with simple lazy routes that import each page. The core of the problem: In the current implementation, without using explicitLinks, regular <a> links on hover and the useNavigate callback load some static chunks for the target route. But I also need to send a request to the BFF to fetch JSON for the store, which should then be merged into the current store. I imagine I’d like to intercept the click/navigation event, send a single request with application/json, show some kind of progress bar (classic for an SPA), wait for the response, and at the moment of the transition merge the new store into the old one. Question: So, how’s the best way to implement this? How do you handle it? Mind sharing your experience? Thanks!
1 Reply
Folco
FolcoOP•2mo ago
I’ve drafted a rough implementation: on link click, we prevent the default behavior, send a request to the BFF, receive a JSON response, and update the store accordingly. It seems like a viable approach, but it does feel a bit hacky. The main question is whether there’s a way to track the preload state so we can display an accurate progress bar. Any ideas?)
import { useNavigate } from '@solidjs/router';

import { setStateAction } from 'src/client/app/store/internal';
import { useDispatch } from 'src/client/hooks/store';

export const useSpaNavigate = () => {
const dispatch = useDispatch();
const navigate = useNavigate();

return async (href: string) => {
try {
const response = await fetch(href, { headers: { Accept: 'application/json' } });
if (!response.ok) {
throw new Error(`Network response was not ok: ${response.status}`);
}
const data = await response.json();
dispatch(setStateAction(data));
} catch (err) {
console.error('Failed to fetch SPA data', err);
}
navigate(href, { replace: true });
};
};
import { useNavigate } from '@solidjs/router';

import { setStateAction } from 'src/client/app/store/internal';
import { useDispatch } from 'src/client/hooks/store';

export const useSpaNavigate = () => {
const dispatch = useDispatch();
const navigate = useNavigate();

return async (href: string) => {
try {
const response = await fetch(href, { headers: { Accept: 'application/json' } });
if (!response.ok) {
throw new Error(`Network response was not ok: ${response.status}`);
}
const data = await response.json();
dispatch(setStateAction(data));
} catch (err) {
console.error('Failed to fetch SPA data', err);
}
navigate(href, { replace: true });
};
};

Did you find this page helpful?