T
TanStack•3mo ago
like-gold

Dynamic environment variables but otherwise CSR

I searched around a bit and read the docs but didn't quite graps things I suppose. Using Router / Start, could we achieve behavior such that: we have dynamic runtime environment variables that are then also provided to the client, but otherwise completely opt out of any server-side rendering logic (and run loaders etc. only on the client). So the only SSR parts would be providing the proper runtime environment data (like an API endpoint, Sentry DSN etc.). This way we would have one build for all environments, but still stick to being almost completely CSR. We currently use Next.js for our management dashboard but we don't need any of the Next.js functionality at all, and would much benefit from just having dynamic environment variables for things that need to be dynamic / set on boot, and otherwise be solely reliant on CSR + having the option of having API routes etc. if need be later (if we don't want to bloat our separate API for a one-and-done thing). I guess this would also fit as a #router-questions , but oh well. :D
6 Replies
like-gold
like-goldOP•3mo ago
Bumping this for visibility.. ā˜ƒļø
metropolitan-bronze
metropolitan-bronze•3mo ago
I have a very similar use case both in past experiences with Next, the desire for as much CSR as possible, and for dynamic environment variables on the client at runtime. Since our deployment strategy is Docker + a Node server, we decided to allow the root route to be server-side rendered, but every other route is not. Setting defaultSsr: false in your createRouter call and then setting ssr: true just on the __root route works well. Then, we extract all VITE_PUBLIC_ (we decided to stick with that convention) environment variables from process.env and serialize them into a base-64 encoded string that gets injected into a <meta> tag when rendering the initial HTML payload. But, beyond that and some other metadata, the initial payload is otherwise an empty shell. To use this data in the app, we use another utility on the client to parse the value in the <meta> tag back into an object. Some kind of getRuntimeEnv utility will be needed to access these because import.meta is going to be all your build-time / statically-transformed environment variables. This approach has been working pretty well in Next, and we've adapted it to TanStack Start pretty easily. Compared to Next, I appreciate how TSS gives you very precise control over where the SSR boundary is. All that said, if its not critical that your runtime environment variables be available to the client immediately, you could implement an API route to serve them and go full SPA mode.
like-gold
like-goldOP•3mo ago
Hmm interesting approach, thanks for the explanation! Sounds like a simple setup to try out as a PoC. Our use cases are pretty much identical to yours. I wonder if the loaders are then run on the client only with defaultSsr=false as well, at least thats what I’d expect when disabling ssr
quickest-silver
quickest-silver•3mo ago
not yet, but i am on that
metropolitan-bronze
metropolitan-bronze•3mo ago
FWIW loaders/beforeLoad is not required for this setup. You can use an async function as the head option when calling createRootRoute and return any <head> tags you want to add. I think its still a good idea to use createIsomorphFn here:
import { createIsomorphicFn } from '@tanstack/react-start'

const getHeadTags = createIsomorphicFn().server(() => {
// Return computed <head> tags.
}).client(() => {
// Do nothing.
})
import { createIsomorphicFn } from '@tanstack/react-start'

const getHeadTags = createIsomorphicFn().server(() => {
// Return computed <head> tags.
}).client(() => {
// Do nothing.
})
__root.tsx
import { createRootRoute, HeadContent, Outlet, Scripts } from '@tanstack/react-router'

function RootComponent() {
return (
<html>
<head>
<HeadContent />
</head>
<body>
<Outlet />
<Scripts />
</body>
</html>
)
}

export const Route = createRootRoute({
head: getHeadTags,
ssr: true,
component: RootComponent
})
import { createRootRoute, HeadContent, Outlet, Scripts } from '@tanstack/react-router'

function RootComponent() {
return (
<html>
<head>
<HeadContent />
</head>
<body>
<Outlet />
<Scripts />
</body>
</html>
)
}

export const Route = createRootRoute({
head: getHeadTags,
ssr: true,
component: RootComponent
})
like-gold
like-goldOP•3mo ago
Didn't think of that, nice! Seems like I have everything I need to know in order to start planning a migration, when loaders can be opted out of being run in the server soon! šŸ˜Ž

Did you find this page helpful?