SSR Cookies

Hi all, I'm new to SSR and don't fully understand in the solidstart basic example which components are being rendered server side / client side. I'm using tanstack solid-query and for testing I have a button that on press calls a /click route on my server and adds a row to the database consisting of: - a deviceId cookie - the current time I then have a /clicks routes that gets the number of clicks for each deviceId (and i use createQuery to use it in solid) Every time the routes /click and /clicks are called, the server checks if deviceId cookie exists and generates a new deviceId if not. This works great if I have a button or something that forces the query to be done client side, since all the client side cookies are passed, however if I try to get the value of that query on the official page load, the cookies aren't passed and so the initial state always begins at 0 My question is is there a way to pass the client cookies when making server side requests?
26 Replies
hannus
hannus6mo ago
I think setCookie and getCookie are useful for your scenario. Import them from vinxi/http rather than h3 because vinxi has wrapped them for better experience. Based on them, you could handle cookie within http request, and they work well with SSR of Solidstart.
hannus
hannus6mo ago
what's more, if you wanna encrypt the cookie, you could try useSession which is a special cookie that encrypt in the server. The example and documents plz check the following link: https://h3.unjs.io/examples/handle-cookie tip: import from vinxi/http rather than h3
Handle Cookie - h3
Use cookies to store data on the client.
icanflyit
icanflyitOP6mo ago
Since my cookie is set server side (from a seperate api server) with set-cookie header, can I still use getCookie on it from the SSR context? Also, when using setCookie and getCookie, is that something that needs to be done before each request? or is there a way to integrate that into fetch so it automatically loads the client cookies when making requests from SSR
Brendonovich
Brendonovich5mo ago
Since you're calling fetch on both the client and the server, you'll need to manually provide the cookies on the server with your own wrapper or something So on the client you don't need to do anything, but on the server use getHeader or getCookie and give the values to fetch
icanflyit
icanflyitOP5mo ago
I've set up my api client (elysiajs eden treaty) to on each server request add headers from getHeaders() and that works great. To make sure response headers get back to my client, do I want to use setHeaders or setResponseHeaders
Brendonovich
Brendonovich5mo ago
either one, they're the same function
icanflyit
icanflyitOP5mo ago
Ah yeah I see, thanks so much I have this now for setting up my api client, does it seem ok?
import { treaty } from "@elysiajs/eden";
import { isServer } from "solid-js/web";
import { parse } from "set-cookie-parser";
import { getHeaders, parseCookies, setCookie, setResponseHeaders } from "vinxi/http";
import { App } from "api";

// Get client cookies from ssr context
function getServerHeaders() {
"use server";
return getHeaders();
}

function setServerHeaders(headers: Headers) {
"use server";
setResponseHeaders(headers);
}

export default treaty<App>('http://localhost:8080', {
fetch: {
credentials: 'include',
},
headers(path, options) {
// If ssr context, copy over client headers.
if (isServer) {
return getServerHeaders();
}
},
onResponse(response) {
// If ssr context, set headers on client.
if (isServer) {
setServerHeaders(response.headers)
}
}
});
import { treaty } from "@elysiajs/eden";
import { isServer } from "solid-js/web";
import { parse } from "set-cookie-parser";
import { getHeaders, parseCookies, setCookie, setResponseHeaders } from "vinxi/http";
import { App } from "api";

// Get client cookies from ssr context
function getServerHeaders() {
"use server";
return getHeaders();
}

function setServerHeaders(headers: Headers) {
"use server";
setResponseHeaders(headers);
}

export default treaty<App>('http://localhost:8080', {
fetch: {
credentials: 'include',
},
headers(path, options) {
// If ssr context, copy over client headers.
if (isServer) {
return getServerHeaders();
}
},
onResponse(response) {
// If ssr context, set headers on client.
if (isServer) {
setServerHeaders(response.headers)
}
}
});
For some reason my client was displaying no content if I didn't add the "use server" directive in my getServerHeaders and setServerHeaders functions (even though I assumed checking isServer before doing a server only action would be enough)
Brendonovich
Brendonovich5mo ago
i'd try avoid the use server if possible those functions are publically exposed maybe lazy import vinxi/http?
icanflyit
icanflyitOP5mo ago
Like so?
export default treaty<App>('http://localhost:8080', {
fetch: {
credentials: 'include',
},
headers(path, options) {
// If ssr context, copy over client headers.
if (isServer) {
import { getHeaders } from "vinxi/http";
return getHeaders();
}
},
onResponse(response) {
// If ssr context, set headers on client.
if (isServer) {
import { setResponseHeaders } from "vinxi/http";
setResponseHeaders(response.headers)
}
}
});
export default treaty<App>('http://localhost:8080', {
fetch: {
credentials: 'include',
},
headers(path, options) {
// If ssr context, copy over client headers.
if (isServer) {
import { getHeaders } from "vinxi/http";
return getHeaders();
}
},
onResponse(response) {
// If ssr context, set headers on client.
if (isServer) {
import { setResponseHeaders } from "vinxi/http";
setResponseHeaders(response.headers)
}
}
});
Brendonovich
Brendonovich5mo ago
const {getHeaders} = await import("vinxi/http")
icanflyit
icanflyitOP5mo ago
Hm that still doesn't work, for some reason a suspense is not resolving when it's like that, I'm not sure exactly how to debug it
icanflyit
icanflyitOP5mo ago
Brendonovich
Brendonovich5mo ago
are the headers logging on the server properly?
icanflyit
icanflyitOP5mo ago
Yeah both when I use "use server" and when I don't they come through correctly from the SSR I don't really understand how to figure out what triggers an error because there are no console logs (in the chrome dev console), I'm not quite sure why but I created an app to test from the bare template and I do get an error in console Here's my bare app.tsx (setup like I originally had with "use server")
import { createSignal } from "solid-js";
import "./app.css";

console.log("before import");
import { getCookie } from "vinxi/http";
console.log("after import");

function testFunc() {
"use server";
getCookie("test");
}

export default function App() {
const [count, setCount] = createSignal(0);

return (
<main>
<h1>Hello world!</h1>
<button class="increment" onClick={() => setCount(count() + 1)} type="button">
Clicks: {count()}
</button>
<p>
Visit{" "}
<a href="https://start.solidjs.com" target="_blank">
start.solidjs.com
</a>{" "}
to learn how to build SolidStart apps.
</p>
</main>
);
}
import { createSignal } from "solid-js";
import "./app.css";

console.log("before import");
import { getCookie } from "vinxi/http";
console.log("after import");

function testFunc() {
"use server";
getCookie("test");
}

export default function App() {
const [count, setCount] = createSignal(0);

return (
<main>
<h1>Hello world!</h1>
<button class="increment" onClick={() => setCount(count() + 1)} type="button">
Clicks: {count()}
</button>
<p>
Visit{" "}
<a href="https://start.solidjs.com" target="_blank">
start.solidjs.com
</a>{" "}
to learn how to build SolidStart apps.
</p>
</main>
);
}
When I load the page ssr works correctly and I get the following output from the server console:
10:23:15 AM [vite] hmr update /Users/elliot/Code/Web/get-cookie/src/app.tsx
before import
after import
10:23:15 AM [vite] hmr update /Users/elliot/Code/Web/get-cookie/src/app.tsx
before import
after import
And the page loads correctly on the client side too, I get the following output in my devtools console
client:495 [vite] connecting...
client:614 [vite] connected.
app.tsx:5 before import
app.tsx:5 after import
client:495 [vite] connecting...
client:614 [vite] connected.
app.tsx:5 before import
app.tsx:5 after import
Brendonovich
Brendonovich5mo ago
just a heads up, before import isn't actually running before the import. all top level imports get hoisted up
icanflyit
icanflyitOP5mo ago
Okay good to know, I noticed before import wasn't being called (but was with lazy importing) Now if I change the testFunc to not use "use server"
function testFunc() {
getCookie("test");
}
function testFunc() {
getCookie("test");
}
I get the same output from the server console (working correctly):
10:26:45 AM [vite] hmr update /Users/elliot/Code/Web/get-cookie/src/app.tsx
before import
after import
10:26:45 AM [vite] hmr update /Users/elliot/Code/Web/get-cookie/src/app.tsx
before import
after import
But I get an error in the browser console
client:495 [vite] connecting...
client:614 [vite] connected.
__vite-browser-external:node:async_hooks:3 Uncaught Error: Module "node:async_hooks" has been externalized for browser compatibility. Cannot access "node:async_hooks.AsyncLocalStorage" in client code. See https://vitejs.dev/guide/troubleshooting.html#module-externalized-for-browser-compatibility for more details.
at Object.get (__vite-browser-external:node:async_hooks:3:11)
at http.js?t=1721928177293&v=2b57a03b:4:166
get @ __vite-browser-external:node:async_hooks:3
(anonymous) @ http.js?t=1721928177293&v=2b57a03b:4
client:495 [vite] connecting...
client:614 [vite] connected.
__vite-browser-external:node:async_hooks:3 Uncaught Error: Module "node:async_hooks" has been externalized for browser compatibility. Cannot access "node:async_hooks.AsyncLocalStorage" in client code. See https://vitejs.dev/guide/troubleshooting.html#module-externalized-for-browser-compatibility for more details.
at Object.get (__vite-browser-external:node:async_hooks:3:11)
at http.js?t=1721928177293&v=2b57a03b:4:166
get @ __vite-browser-external:node:async_hooks:3
(anonymous) @ http.js?t=1721928177293&v=2b57a03b:4
Brendonovich
Brendonovich5mo ago
yeah that's expected vinxi/http only works on the server
icanflyit
icanflyitOP5mo ago
But I'm never calling testFunc()
Brendonovich
Brendonovich5mo ago
vinxi/http is importing node:async_hooks which is bundled into the client and produces that error
icanflyit
icanflyitOP5mo ago
How would I implement that here so that I only call it on the server without using use_server
Brendonovich
Brendonovich5mo ago
i'd figure out how to make it work with lazy imports lol
icanflyit
icanflyitOP5mo ago
Wait is the idea that the lazy imports are not top level
Brendonovich
Brendonovich5mo ago
yeah
icanflyit
icanflyitOP5mo ago
That makes a lot more sense lol Wait and when I use "use server", is that making an endpoint the client can call?
Brendonovich
Brendonovich5mo ago
yeah
icanflyit
icanflyitOP5mo ago
I have it all working now, thanks so much :) I'm still confused why my errors aren't being printed to console (Any error thrown inside a component shows an error on a screen but top level errors seem to just dissapear (and cause everything in that suspense to not render) I have solid-devtools and tanstack query devtools so I'm not sure if one of those is changing error handling (or if it's somewhere else in my project)
Want results from more Discord servers?
Add your server