S
SolidJS4w ago
oke

Generate static UI elements from server data only ONCE

I want to generate my navbar components based on my server folder structure Specifically, I have folder countries which contains file us.json and ca.json. For each file in folder countries, I want to generate an <A> element onto the Layout component. I struggle to know how to "send" this data from server to client and construct the UI, but only ONCE at startup - since the countries folder is static Stackblitz: https://stackblitz.com/~/github.com/bhuynhdev/solid-start-test My current code doesn't work. Locally, the page displays, but the Router just doesn't work - clicking on links only change the URL but doesn't update page content
// (countries).tsx
import { For } from "solid-js";
import { basename, extname } from "path";
import { A } from "@solidjs/router";

function extractName(p) {
return basename(p, extname(p));
}

const links = Object.keys(import.meta.glob("../countries/*")).map((path) => {
const name = extractName(path);
return { title: name, href: `/${name}` };
});

export default function Layout(props) {
return (
<main>
<h1>Hello world!</h1>
<ul>
<For each={links}>
{(link) => (
<li>
<A href={link.href}>Visit country {link.title}</A>
</li>
)}
</For>
{props.children}
</ul>
</main>
);
}
// (countries).tsx
import { For } from "solid-js";
import { basename, extname } from "path";
import { A } from "@solidjs/router";

function extractName(p) {
return basename(p, extname(p));
}

const links = Object.keys(import.meta.glob("../countries/*")).map((path) => {
const name = extractName(path);
return { title: name, href: `/${name}` };
});

export default function Layout(props) {
return (
<main>
<h1>Hello world!</h1>
<ul>
<For each={links}>
{(link) => (
<li>
<A href={link.href}>Visit country {link.title}</A>
</li>
)}
</For>
{props.children}
</ul>
</main>
);
}
15 Replies
oke
okeOP4w ago
// (countries)/[country].tsx
import { useParams } from '@solidjs/router';

export default function () {
const params = useParams();
return <p>Welcome to {params.country}</p>;
}
// (countries)/[country].tsx
import { useParams } from '@solidjs/router';

export default function () {
const params = useParams();
return <p>Welcome to {params.country}</p>;
}
I could make a getLinks query-wrapped function and expose the data via a createAsync. But the cache would only be 5 minutes, thus getLinks is bound to be called again. I want to explore a way to do this processing only absolutely ONE time
Brendonovich
Brendonovich4w ago
This doesn't work as you're importing Node's path library on the client The cache provided by query is only per-request on the server and for like 15 seconds on the client, with this approach you'd probably want to add a cache-control header A halfway point would be to generate links in a vite plugin and expose it via a virtual module. The array would be generated at build time, then it'd be used on the server to render Layout each time, and would be shipped to the client as an immutably cached file. Not quite the 'generate static UI elements from server data' but closer than using a server function
oke
okeOP4w ago
I am interested in the "cache-control" header solution. Do you know of a way to add cache-control header to query functions ?
Brendonovich
Brendonovich4w ago
Return a web Response from the server function that has the header Either with new Response or the json/redirect/reload helpers from solid router
peerreynders
peerreynders4w ago
But the cache would only be 5 minutes
Actually the cache is indefinite as long as there is at least one createAsync referencing it. - 5 seconds is the time limit to between a route preload and the first createAsync to use the warmed query. As long as there is at least one single active createAsync, it is assumed that the client is responsible for invalidating the query whenever necessary. It's only when the reference count drops to 0 that the query will rerun on the next access. - 5 minutes is the time limit before an unreferenced cached value is deleted. This really only affects bfcache; i.e. code that runs after “go back”. https://discord.com/channels/722131463138705510/1336421521232494654/1336754105791676466
oke
okeOP4w ago
Oh I see. Since this getLinks createAsync would live in the root layout (and the NavBar is always present on every page), there will always be at least one createAsync referencing the cache. And since arguments to the getLinks function won't change, this cache will be indefinite without needing to use cache-control header. I assume this indefinite cache lives on the server memory, not the browser cache ?
peerreynders
peerreynders4w ago
query is a client side construct. The cached values live in the client's JS memory space.
GitHub
solid-router/src/data/query.ts at 50c5d7bdef6acc5910c6eb35ba6a24b15...
A universal router for Solid inspired by Ember and React Router - solidjs/solid-router
peerreynders
peerreynders4w ago
(and the NavBar is always present on every page)
In many applications the Not Found page lives outside of the rest of the app tree. In that case one navigation to Not Found is enough for the count to drop to 0.
oke
okeOP4w ago
Thanks a lot. That will work enough for me for now. The frequency of accessing "Not Found" is rare so I can take this trade-off Overall, it seems like there is currently no way for the SSR to grab server data and directly serializes the data into the markup one-time as truly static HTML strings.
peerreynders
peerreynders4w ago
The benefit of cache-control is that it is stored in the browser cache but to use it effectively the fetches need to appear identical to the browser. At that point it may make sense to create an API Route to make it easy to control the "shape of the fetch" from the client (for the benefit of the browser). Once the response is properly cached and it's ensured that every fetch from the query looks identical from the browser's point of view even if the query is rerun the cached response is used.
MDN Web Docs
Cache-Control - HTTP | MDN
The HTTP Cache-Control header holds directives (instructions) in both requests and responses that control caching in browsers and shared caches (e.g., Proxies, CDNs).
peerreynders
peerreynders4w ago
no way for the SSR to grab server data and directly serializes into the markup one-time as truly static HTML strings.
To take advantage of the hydration process you would have to cache the map (by country) data at the application root inside a context. Basically the Provider could manage a store that gets filled with country data as needed. I suspect that whatever country data was used for SSR would end up in the hydration data to initialize the store; though I think an experiment to verify this would be advisable as details of how the store is used may impact whether it gets hydrated or not.
oke
okeOP4w ago
So if I understand this correctly, you meant serializing the data not into the markup but into a ContextProvider ?
peerreynders
peerreynders4w ago
Basically I'm trying to piggyback on the hydration of the reactive graph.
oke
okeOP4w ago
Thanks. I will probably not try to mess with the hydration of the reactive graph. I slowly have come to accept the realization that, maybe I the way I wanted to use Solid Start for my scenario was not correct/was not its intended use case, and if my data is very static, maybe I should just generate a JSON file once on server start, then import it for re-use I wonder if React Server Component could "solve" my use case: generating the actual HTML content once on the server, then just sends the static HTML string back --> no data fetching is needed
peerreynders
peerreynders4w ago
Nope. RSCs don't create HTML; that's a common over simplification; they create ReactNodes. Because RSCs can ship props to client side components ReactNodes + HTML are sent to the client; the only thing you are saving is the JS that generates the ReactNodes. Pure HTML requires Server Islands/Lakes ((client )islands are typically associated with MPA routing, i.e. route navigations are always server rendered).

Did you find this page helpful?