peerreynders
peerreynders
SSolidJS
Created by ⱼ ₒ ₑ on 1/17/2025 in #support
Solid Start Vinxi complaining about public and can't resolve public fonts
You can suppress the message with this app.config.ts:
import { defineConfig } from "@solidjs/start/config";

export default defineConfig({
vite: {
build: {
rollupOptions: {
external: [
'/fonts/Geist-SemiBold.woff2'
]
}
}
}
});
import { defineConfig } from "@solidjs/start/config";

export default defineConfig({
vite: {
build: {
rollupOptions: {
external: [
'/fonts/Geist-SemiBold.woff2'
]
}
}
}
});
https://rollupjs.org/configuration-options/#external That way it will only complain about files you haven't added yet (warning you of potential misspellings).
8 replies
SSolidJS
Created by ⱼ ₒ ₑ on 1/17/2025 in #support
Solid Start Vinxi complaining about public and can't resolve public fonts
Given that font files are not bundled, no. What's important is that they can be located at runtime. You can use preload to give the browser a hint that it will need the font files.
8 replies
SSolidJS
Created by Josesito on 1/18/2025 in #support
How does `query` cache actually work?
Even if the routes get loaded from different browsers.
The query values exist within the client side JS scope of the browser tab; so each browser, even browser tab, will issue a separate request creating their own isolated query values. Now if you navigate inside the same browser tab within the 5 secs window to the second route, I assume that the query request won't be repeated. The query values live here
16 replies
SSolidJS
Created by ⱼ ₒ ₑ on 1/17/2025 in #support
Solid Start Vinxi complaining about public and can't resolve public fonts
- Created and installed fresh SolidStart(TS) "basic" project. - moved src/app.css to src/styles/app.css - adjusted import './styles/app.css'; in src/app.tsx - copied Geist-SemiBold.woff2 into public/fonts - modified src/styles/app.css:
/* file: src/styles/app.css */
/* added @font-face */
@font-face {
font-family: 'Geist';
src: url('/fonts/Geist-SemiBold.woff2') format('woff2');
font-weight: 600;
font-style: normal;
font-display: swap;
}

body {
font-family: Gordita, Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
}

a {
margin-right: 1rem;
}

main {
text-align: center;
padding: 1em;
margin: 0 auto;
}

h1 {
/* added font-family */
font-family: Geist;
color: #335d92;
text-transform: uppercase;
font-size: 4rem;
line-height: 1.1;
margin: 4rem auto;
max-width: 14rem;
}
/* … more css … */
/* file: src/styles/app.css */
/* added @font-face */
@font-face {
font-family: 'Geist';
src: url('/fonts/Geist-SemiBold.woff2') format('woff2');
font-weight: 600;
font-style: normal;
font-display: swap;
}

body {
font-family: Gordita, Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
}

a {
margin-right: 1rem;
}

main {
text-align: center;
padding: 1em;
margin: 0 auto;
}

h1 {
/* added font-family */
font-family: Geist;
color: #335d92;
text-transform: uppercase;
font-size: 4rem;
line-height: 1.1;
margin: 4rem auto;
max-width: 14rem;
}
/* … more css … */
- $ pnpm run dev and visit http://localhost:3000/. It works. - $ pnpm run build. You'll see a bunch of
/fonts/Geist-SemiBold.woff2 referenced in /fonts/Geist-SemiBold.woff2 didn't resolve at build time, it will remain unchanged to be resolved at runtime
- $ node .output/server/index.mjs and visit http://localhost:3000/. It works.
8 replies
SSolidJS
Created by Josesito on 1/18/2025 in #support
How does `query` cache actually work?
Just to add to what has already been stated. These are two separate requests:
http://localhost:3000/product/thang
http://localhost:3000/product/foo
http://localhost:3000/product/thang
http://localhost:3000/product/foo
but more importantly
export const getProduct = query(async (slug: string) => {
"use server";
try {
return await directus.request(readItem("product", slug));
} catch (e) {
return null;
}
}, "productById")
export const getProduct = query(async (slug: string) => {
"use server";
try {
return await directus.request(readItem("product", slug));
} catch (e) {
return null;
}
}, "productById")
initiates it's own RPC request based on the route parameter. You can can still use createAsync(() => getProduct('foo')) and createAsync(() => getProduct('thang')) on the same route/page, each issuing their own, independent request. But getProduct takes an argument. query values are partitioned by a hash key based on the arguments that are used to acquire them. That's why it is important to use the query's keyFor to target the correct partitioned value that is stored under the query. At the end of an action
return revalidate(getProduct.keyFor('foo'), true);
return revalidate(getProduct.keyFor('foo'), true);
would only rerun the query function for 'foo' but not for 'thang' (driving components connected to the 'foo' value but not components connected to the 'thang' value).
16 replies
SSolidJS
Created by Josesito on 1/18/2025 in #support
How does `query` cache actually work?
A query value is considered "fresh" for 5 seconds. All components drawing their data from that query within 5 seconds will be served the same data from the initial request. Should any component connect after 5 seconds, an entirely new request is run. https://github.com/solidjs/solid-router/blob/50c5d7bdef6acc5910c6eb35ba6a24b15aae3ef6/src/data/query.ts#L15-L16 For use in the context of bfcache every 5 minutes query values older than 3 minutes are completely removed.
16 replies
SSolidJS
Created by Josesito on 1/18/2025 in #support
How does `query` cache actually work?
That's not entirely accurate. The intention is that the query value is "global" for the duration of the "route request"; i.e. until the route data settles after navigation to that route. After that actions can respond with data for multiple querys in a single request (aka single flight mutation).
16 replies
SSolidJS
Created by Josesito on 1/18/2025 in #support
How does `query` cache actually work?
- actions tend to mutate server state that will require some querys on the route to rerun. At the end of an action revalidate is used to refresh the relevant querys. - When a query is refreshed all the components on the route depending on it will receive the up-to-date data. - These actions can send the fresh data for the affected querys in their response body; this is referred to as singleflight mutations.
16 replies
SSolidJS
Created by Josesito on 1/18/2025 in #support
How does `query` cache actually work?
It's a mistake to view query in isolation. - To some degree the aim is for the route to be able to load all the data that may be needed by every component that is on it's page. - From that perspective query helps to decouple the aquisition of data from the component consuming it. The component then can simply access the data it needs via createAsync. - So more than one component on the page can consume that data without multiple fetches. This is the "request deduping". More fundamentally this "1 to N fan-out", i.e. 1 set of data driving N components has some benefit later with actions. The data can be shared among components without going through context. - At least with Solid 1.x the query / createAsync seam establishes the "asynchronous value" to "synchronous reactivity" boundary into Solid's reactive graph where you compose reactive primitives. query deals with the "value asynchronously" while createAsync makes that value accessible via a reactive accessor. - Ideally you'll want to "warm up" all the querys on the route in the route's preload. As soon as the cursor hovers over a link to the route the preload querys will start their loading process in an attempt to shorten the loading time when the route's link is clicked.
16 replies
SSolidJS
Created by quinnvaughn on 1/18/2025 in #support
Any way to redo this logic without an infinite loop?
Perhaps something else is going on. This doesn't cause an infinite loop:
// file: src/routes/about.tsx
import { For, Show } from 'solid-js';
import { createAsync } from '@solidjs/router';
import { Title } from '@solidjs/meta';

type DataType = { id: number; name: string };

async function getData() {
'use server';
await new Promise((resolve) => setTimeout(resolve, 300));

return [
{ id: 0, name: 'zero' },
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
}

export default function About() {
const data = createAsync(getData);

return (
<main>
<Title>About</Title>
<h1>About</h1>
<Show when={data()}>
{(items) => (
<For each={items()}>{(item) => <div>{item.name}</div>}</For>
)}
</Show>
</main>
);
}
// file: src/routes/about.tsx
import { For, Show } from 'solid-js';
import { createAsync } from '@solidjs/router';
import { Title } from '@solidjs/meta';

type DataType = { id: number; name: string };

async function getData() {
'use server';
await new Promise((resolve) => setTimeout(resolve, 300));

return [
{ id: 0, name: 'zero' },
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
];
}

export default function About() {
const data = createAsync(getData);

return (
<main>
<Title>About</Title>
<h1>About</h1>
<Show when={data()}>
{(items) => (
<For each={items()}>{(item) => <div>{item.name}</div>}</For>
)}
</Show>
</main>
);
}
14 replies
SSolidJS
Created by ⱼ ₒ ₑ on 1/17/2025 in #support
How to use server function & suspense
- Created fresh SolidStart(TS) "basic" template - Dropped the following into src/routes/about.tsx - Just works.
// file: src/routes/about.tsx
import { Suspense, Show } from 'solid-js';
import { createAsync } from '@solidjs/router';
import { Title } from '@solidjs/meta';

async function getUserInfo() {
'use server';
await new Promise((resolve) => setTimeout(resolve, 300));

console.log('getUserInfo', new Date().toString());
return {
avatar: 'https://placehold.co/100x100/93c5fd/ffffff?text=Generic Avatar',
name: 'User Name',
};
}

export default function About() {
const fetchedInfo = createAsync(getUserInfo);

return (
<main>
<Title>About</Title>
<h1>About</h1>
<Suspense fallback={'loading'}>
<Show when={fetchedInfo()}>
{(user) => (
<>
<img
src={user().avatar}
decoding="sync"
loading="eager"
alt="User Avatar"
draggable="false"
/>
<span>
<p>{user().name}</p>
<p>{user().email}</p>
</span>
</>
)}
</Show>
</Suspense>
</main>
);
}
// file: src/routes/about.tsx
import { Suspense, Show } from 'solid-js';
import { createAsync } from '@solidjs/router';
import { Title } from '@solidjs/meta';

async function getUserInfo() {
'use server';
await new Promise((resolve) => setTimeout(resolve, 300));

console.log('getUserInfo', new Date().toString());
return {
avatar: 'https://placehold.co/100x100/93c5fd/ffffff?text=Generic Avatar',
name: 'User Name',
};
}

export default function About() {
const fetchedInfo = createAsync(getUserInfo);

return (
<main>
<Title>About</Title>
<h1>About</h1>
<Suspense fallback={'loading'}>
<Show when={fetchedInfo()}>
{(user) => (
<>
<img
src={user().avatar}
decoding="sync"
loading="eager"
alt="User Avatar"
draggable="false"
/>
<span>
<p>{user().name}</p>
<p>{user().email}</p>
</span>
</>
)}
</Show>
</Suspense>
</main>
);
}
9 replies
SSolidJS
Created by ⱼ ₒ ₑ on 1/17/2025 in #support
How to use server function & suspense
Just to be clear you are using SolidStart (which is required for "use server" RPC)? Usually people throw the SolidStart tag onto the topic.
9 replies
SSolidJS
Created by snnsnn on 1/17/2025 in #support
Best Practices for Handling Errors in a query-Wrapped Server Function
7 replies
SSolidJS
Created by snnsnn on 1/17/2025 in #support
Best Practices for Handling Errors in a query-Wrapped Server Function
The important feature is that the Error is returned, not thrown. This means that in the login.tsx route we can use the latest submission;
const loggingIn = useSubmission(loginOrRegister);
const loggingIn = useSubmission(loginOrRegister);
to obtain the most recent error from the submission record to display the error:
<Show when={loggingIn.result}>
{(result) => (
<p
class="text-red-500 text-center"
role="alert"
id="error-message"
>
{result().message}
</p>
)}
</Show>
<Show when={loggingIn.result}>
{(result) => (
<p
class="text-red-500 text-center"
role="alert"
id="error-message"
>
{result().message}
</p>
)}
</Show>
So either the login succeeds and you are navigated to / or the Error message is simply displayed in the current /login page.
7 replies
SSolidJS
Created by snnsnn on 1/17/2025 in #support
Best Practices for Handling Errors in a query-Wrapped Server Function
Finally the login.tsx route uses loginOrRegister:
export const loginOrRegister = action(async (formData: FormData) => {
"use server";
const email = String(formData.get("email"));
const password = String(formData.get("password"));
const loginType = String(formData.get("loginType"));
let error = validateEmail(email) || validatePassword(password);
if (error) return new Error(error);

try {
const user = await (loginType !== "login"
? register(email, password)
: login(email, password));
await setAuthOnResponse(user.id);
} catch (err) {
return err as Error;
}
throw redirect("/");
});
export const loginOrRegister = action(async (formData: FormData) => {
"use server";
const email = String(formData.get("email"));
const password = String(formData.get("password"));
const loginType = String(formData.get("loginType"));
let error = validateEmail(email) || validatePassword(password);
if (error) return new Error(error);

try {
const user = await (loginType !== "login"
? register(email, password)
: login(email, password));
await setAuthOnResponse(user.id);
} catch (err) {
return err as Error;
}
throw redirect("/");
});
Again there are only two outcomes: - resolve to an Error instance - reject with a redirect compelling the client side router to navigate to /
7 replies
SSolidJS
Created by snnsnn on 1/17/2025 in #support
Best Practices for Handling Errors in a query-Wrapped Server Function
Now there is a counterpart:
export const redirectIfLoggedIn = query(async () => {
"use server";

let userId = await getAuthUser();
if (userId) {
throw redirect("/");
}
return null;
}, "loggedIn");
export const redirectIfLoggedIn = query(async () => {
"use server";

let userId = await getAuthUser();
if (userId) {
throw redirect("/");
}
return null;
}, "loggedIn");
This is part of the login route preload:
export const route = {
preload: () => redirectIfLoggedIn(),
} satisfies RouteDefinition;
export const route = {
preload: () => redirectIfLoggedIn(),
} satisfies RouteDefinition;
This ensures that you are automatically navigated from /login to / if there is an authenticated session.
7 replies
SSolidJS
Created by snnsnn on 1/17/2025 in #support
Best Practices for Handling Errors in a query-Wrapped Server Function
There are only two outcomes, either: - resolve to the user info - reject with a redirect compelling the client side router to navigate to /login (or simply stay there) getUser() is used on the lop level Layout:
const user = createAsync(() => getUser());
const user = createAsync(() => getUser());
This accomplishes two things:
<Show when={user()} fallback={<A href="/login">Login</A>}>
<form action={logout} method="post">
<button name="logout" type="submit">
Logout
</button>
</form>
</Show>
<Show when={user()} fallback={<A href="/login">Login</A>}>
<form action={logout} method="post">
<button name="logout" type="submit">
Logout
</button>
</form>
</Show>
- it makes the user accessor available to show the correct "Login" vs. "Logout" - but more importantly it always throws a redirect('/login') whenever there isn't an active account ID in the session cookie. So you either stay on the /login page or you will get navigated to the /login page.
7 replies
SSolidJS
Created by snnsnn on 1/17/2025 in #support
Best Practices for Handling Errors in a query-Wrapped Server Function
TL;DR: Return Errors; throw redirects I assume this comes from the with-auth example. To be honest that version doesn't make a lot of sense to me as the "User not found " Error would simply get swallowed in favour of the thrown redirect. Instead have a look at how the strello app handles it with a more distributed approach:
export const getUser = query(async () => {
"use server";
const userId = await getAuthUser();
if (!userId) throw redirect("/login");
const user = await db.account.findUnique({ where: { id: userId } });
if (!user) throw redirect("/login");
return { id: user.id, email: user.email };
}, "user");
export const getUser = query(async () => {
"use server";
const userId = await getAuthUser();
if (!userId) throw redirect("/login");
const user = await db.account.findUnique({ where: { id: userId } });
if (!user) throw redirect("/login");
return { id: user.id, email: user.email };
}, "user");
7 replies
SSolidJS
Created by snnsnn on 1/15/2025 in #support
Seeking Insights on SolidStart Server Function Issue
My hypothesis that the refresh is SSRed entirely in the route worker, so even the "use server" sections are running just in the route worker accessing the route worker's store.
7 replies
SSolidJS
Created by snnsnn on 1/15/2025 in #support
Seeking Insights on SolidStart Server Function Issue
What I cannot explain is why/how getAuthor accesses the first store on refresh.
7 replies