Compile specific routes into static html

I'm building an app that has the following routes: - home - about - contact - login - dashboard The dashboard is really the only "dynamic" part of the entire app and it should only be accessible after a user logs in. Question: Is it possible to compile my app so that home, about, and login are static html pages and dashboard is the only page served as a "SPA"? I'm interested in doing this to optimize for SEO and page load speed.
9 Replies
exercise
exercise4w ago
export default defineConfig({ server: { prerender: { routes: ["/about", "/login"] } } })
export default defineConfig({ server: { prerender: { routes: ["/about", "/login"] } } })
add this to your app.config
MicroDev
MicroDev4w ago
Hi, I too am looking for a similar setup: Is there a way to implement pre-rendering with a SPA fallback as described in the React Router docs — but with multiple statically rendered entry points and, importantly, no server-side rendering (SSR)? The goal is to support fully static deployment.
<Router root={...}>
<Route path='/' pre-render />
<Route path='/about' pre-render />
<Route path='/course/:slug' />
</Router>
<Router root={...}>
<Route path='/' pre-render />
<Route path='/about' pre-render />
<Route path='/course/:slug' />
</Router>
Outputs:
dist/
├─ assets/
├─ index.css
├─ index.js
├─ about.js
└─ course.js
├─ index.html
├─ about.html
└─ spa-fallback.html
dist/
├─ assets/
├─ index.css
├─ index.js
├─ about.js
└─ course.js
├─ index.html
├─ about.html
└─ spa-fallback.html
Madaxen86
Madaxen864w ago
Have you tried the nitro pre-render config where you can include or exclude specific routes.
MicroDev
MicroDev4w ago
Thanks @Madaxen86 and @exercise. I'm using SolidStart with Nitro as the underlying engine, and I'm explicitly defining certain routes to be pre-rendered. The build process generates both server-side and client-side bundles. However, I ignore the server-side output and only use the contents of .output/public. With some custom scripting and file rearrangement, I handle routing through the CDN to achieve the desired behaviour. For SPA fallback, I've defined an empty /spa-fallback route. I then modify the generated index.html from this route to match the main SPA index.html, effectively acting as a catch-all fallback. There are still a few bugs to resolve, particularly when porting a client-side rendered app to server-side rendering. One key issue is preventing the use of browser-specific APIs on pre-rendered routes. Is isServer the correct approach to guard components so that they only render in the browser, similar to use client directive?
Madaxen86
Madaxen864w ago
With isServer you'll run into hydration issues. import components with clientOnly instead: https://docs.solidjs.com/solid-start/reference/client/client-only#clientonly (will trigger Suspense). Or you can create a wrapper which renders components only after the app has mounted on the client:
function ClientOnly(props:ParentProps){
const [isMounted,setIsMounted] = createSignal(false);
onMount(() => setIsMounted(true));
return <Show when={isMounted()} fallback={"...loading"}>{props.children}</Show>
}
function ClientOnly(props:ParentProps){
const [isMounted,setIsMounted] = createSignal(false);
onMount(() => setIsMounted(true));
return <Show when={isMounted()} fallback={"...loading"}>{props.children}</Show>
}
clientOnly - Solid Docs
Documentation for SolidJS, the signals-powered UI framework
indigo.snowboll
How do I use this with the homepage, esp if I want to have a public landing page and an authenticated only home page (both at /) ?
exercise
exercise4w ago
it doesn't make sense to prerender a page that you'll have logic like this on
indigo.snowboll
Thank you. Yes. I'm doing just that. I'm now running into a different issue that seems to be a bug (in vinxi? IDK). I'm getting a "Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client". My app.tsx looks like this:
export default function App() {
return (
<Suspense>
<Router root={Layout}>
<FileRoutes />
</Router>
</Suspense>
);
}
export default function App() {
return (
<Suspense>
<Router root={Layout}>
<FileRoutes />
</Router>
</Suspense>
);
}
I reduced my Layout (causing the error) to just this:
export default function Layout(props: RouteSectionProps) {
const user = createAsync(() => getUser(), {deferStream: true}); // If I remove this like, the code runs
const location = useLocation();

return (
<div>
Hello world!
</div>
);
}
export default function Layout(props: RouteSectionProps) {
const user = createAsync(() => getUser(), {deferStream: true}); // If I remove this like, the code runs
const location = useLocation();

return (
<div>
Hello world!
</div>
);
}
I need to get the user (from the session) so that I can then render a private or public homepage accordingly. (Problem went away, also, had a lot of hydration issues, also related to missing Suspense blocks.)

Did you find this page helpful?