ChrisThornham
ChrisThornham
SSolidJS
Created by ChrisThornham on 1/21/2025 in #support
Error when evaluating SSR module EVEN WITH ssr: false
I have a SolidStart project with file-based routing and ssr: false I'm using @revenuecat/purchases-capacitor inside of the project. When I use the revenuecat package in a file inside of the routes folder, everything works. Unfortunately, using the revenuecat package outside of the routes folder throws and error: Error when evaluating SSR module /node_modules/@solidjs/start/dist/server/spa/handler.js: failed to import "@revenuecat/purchases-capacitor" I'm guessing that's because SolidStart only considers files inside of the routes folder to be "client side" even if I have ssr: false. Is this a correct assumption? Any clarity would be appreciated. For context, I was trying to use the revenuecat package in an auth-context file that I provide to the app like this:
<Router
root={(props) => (
<MetaProvider>
<SupabaseAuthProvider> // I'M USING THE REVENUECAT PACKAGE HERE
<ThemeProvider>
<DeviceProvider>
<Title>{brandName}</Title>
<PostHogPageView />
<Suspense>{props.children}</Suspense>
</DeviceProvider>
</ThemeProvider>
</SupabaseAuthProvider>
</MetaProvider>
)}
>
<FileRoutes />
</Router>
<Router
root={(props) => (
<MetaProvider>
<SupabaseAuthProvider> // I'M USING THE REVENUECAT PACKAGE HERE
<ThemeProvider>
<DeviceProvider>
<Title>{brandName}</Title>
<PostHogPageView />
<Suspense>{props.children}</Suspense>
</DeviceProvider>
</ThemeProvider>
</SupabaseAuthProvider>
</MetaProvider>
)}
>
<FileRoutes />
</Router>
Thanks!
1 replies
SSolidJS
Created by ChrisThornham on 12/5/2024 in #support
Help: Preflight OPTIONS Requests In SolidStart. I'm Lost!
The docs give the following examples for methods in API routes.
export function GET() {
// ...
}

export function POST() {
// ...
}

export function PATCH() {
// ...
}

export function DELETE() {
// ...
}
export function GET() {
// ...
}

export function POST() {
// ...
}

export function PATCH() {
// ...
}

export function DELETE() {
// ...
}
Does OPTIONS() also exist for preflight requests? Implemented something like this:
export async function OPTIONS() {
return new Response(null, {
status: 204, // No Content
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET,HEAD,POST,OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
"Access-Control-Max-Age": "86400", // Cache preflight response for 24 hours
},
});
}
export async function OPTIONS() {
return new Response(null, {
status: 204, // No Content
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET,HEAD,POST,OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
"Access-Control-Max-Age": "86400", // Cache preflight response for 24 hours
},
});
}
18 replies
SSolidJS
Created by ChrisThornham on 11/29/2024 in #support
Vercel & CORS Problem
Does anyone know how to configure CORS on Vercel with SolidStart? I keep getting...
Access to fetch at 'https://my-vercel-url.app/api/todos/get-todos' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
What I've Tried 1. I tried adding a vercel.json file at the root of my project: https://vercel.com/guides/how-to-enable-cors#enabling-cors-using%C2%A0vercel.json 2. I've tried adding headers to my API endpoint:
export async function GET({ request }: APIEvent) {
// Fetch todos from external API
const response = await fetch("https://jsonplaceholder.typicode.com/todos");
const todos = await response.json();

// Create a new response with CORS headers
return new Response(JSON.stringify(todos), {
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*', // Allows all origins (use carefully in production)
'Access-Control-Allow-Methods': 'GET,HEAD,POST,OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
},
});
}
export async function GET({ request }: APIEvent) {
// Fetch todos from external API
const response = await fetch("https://jsonplaceholder.typicode.com/todos");
const todos = await response.json();

// Create a new response with CORS headers
return new Response(JSON.stringify(todos), {
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*', // Allows all origins (use carefully in production)
'Access-Control-Allow-Methods': 'GET,HEAD,POST,OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
},
});
}
3. I've set my server to "vercel" in app.config.ts
import { defineConfig } from "@solidjs/start/config";

export default defineConfig({
ssr: false,
server: { preset: "vercel" },
});
import { defineConfig } from "@solidjs/start/config";

export default defineConfig({
ssr: false,
server: { preset: "vercel" },
});
And nothing. Any help would be appreciated. Chris
3 replies
SSolidJS
Created by ChrisThornham on 11/27/2024 in #support
API Endpoint Security Question
I'm converting some server functions to API endpoints. With server function, I use "use server" to keep sensitive data like API keys secure. Here's an example.
"use server";

// My Stripe secret key is secure because of "use server"
const stripe = new Stripe(process.env.STRIPE_SK!, {
apiVersion: "2024-10-28.acacia",
});

// Create user
export async function createStripeCustomer(email: string) {
// create stripe customer
}
"use server";

// My Stripe secret key is secure because of "use server"
const stripe = new Stripe(process.env.STRIPE_SK!, {
apiVersion: "2024-10-28.acacia",
});

// Create user
export async function createStripeCustomer(email: string) {
// create stripe customer
}
If I create an API endpoint to do the same thing, is my Secret Key secure without using "use server"? I think that API routes inherently run on the server, meaning the code within them is not exposed to the client. Am I right?
// Is this Stripe secret key secure if I don't use "use server"
const stripe = new Stripe(process.env.STRIPE_SK!, {
apiVersion: "2024-10-28.acacia",
});

export async function POST({ request }: APIEvent) {
// create stripe customer.
}
// Is this Stripe secret key secure if I don't use "use server"
const stripe = new Stripe(process.env.STRIPE_SK!, {
apiVersion: "2024-10-28.acacia",
});

export async function POST({ request }: APIEvent) {
// create stripe customer.
}
3 replies
SSolidJS
Created by ChrisThornham on 11/26/2024 in #support
Help Configuring SolidStart with Capacitor JS
I'm trying to turn a SolidStart project into a Capacitor app so I can create mobile apps for iPhone and Android. I’m new to this and struggling to fully understand how to integrate Capacitor with my SolidStart app. I have a few questions I hope someone can help clarify: 1. Capacitor and SSR/CSR: - Once a Capacitor app is packaged, it’s essentially a client-side app running on a mobile device. So, I’m assuming that my app must be CSR (Client-Side Rendered) when using Capacitor, not SSR (Server-Side Rendered), correct? 2. Index.html and Capacitor Configuration: Capacitor requires a separate directory for built web assets (such as dist or www) and an index.html file at the root of this directory. I’ve read that the index.html file must have a <head> tag in order for Capacitor plugins to work properly. Here’s where I’m confused: - My SolidStart project has a <head> tag inside entry-server.tsx in the /src directory. Is this the correct place to make my edits for the <head> tag? - Alternatively, do I need to configure the app.config.ts file in a certain way, then run npm run build, and then manually edit the index.html file that gets output by that build command? 3. Server Preset What server preset should I use in app.config.ts?
export default defineConfig({
ssr: true,
server: { preset: "What goes here?" },
});
export default defineConfig({
ssr: true,
server: { preset: "What goes here?" },
});
Thanks in advance for your help!
6 replies
SSolidJS
Created by ChrisThornham on 10/28/2024 in #support
RPC "use server" and CSRF Attacks
I’m looking to better understand the CSRF risks associated with SolidStart, specifically regarding the use of RPC calls with the "use server" function. In Next.js, when I process a form with server actions, the risk of CSRF attacks is significantly reduced for a few reasons: 1. Server actions are limited to POST requests. 2. Modern browsers enforce Same-Site cookies by default, which helps mitigate CSRF vulnerabilities. 3. I can further enhance security by ensuring that all cookies have the SameSite=Strict, HttpOnly, and Secure settings. With SolidStart, using "use server" means I’m making an RPC call to that function. It's my understanding that RPC calls use HTTP POST to invoke specific server-side functions by name. Given this, I believe the same three points regarding CSRF risk reduction should apply to SolidStart as well. Am I correct in my understanding? If not, what potential CSRF risks should I be aware of when using RPC calls in SolidStart? Thank you! Chris
5 replies
SSolidJS
Created by ChrisThornham on 10/3/2024 in #support
SRR And Web Crawlers. Help Me Understand
I just released a new business that has more than 150 pages of docs. I custom-coded the entire site and docs section using SolidStart. I have SSR turned on. Here's my issue. Crawlers aren't finding my pages. I'll give two examples. 1. Algolia just added a crawler to my site. Their crawler only found 2 pages, not 150. 2. I tried to use a sitemap generator. Same problem. It only found 2 or 3 pages. Can anyone help me understand why crawlers aren't finding my pages and how I can fix this? Either I screwed something up, or I fundamentally don't understand how this works. My site address is: https://www.quickstartjs.com Thanks! Chris
5 replies
SSolidJS
Created by ChrisThornham on 9/13/2024 in #support
Can I Pass `createAsync()` a Signal?
I'm building an analytics dashboard with the Plausible API. Their API let's you set the time period for the data you're requesting by appending a period value to your URL string like &period=6m0.
I want to dynamically change this period value when clicking buttons (e.g., "12mo", "month", "7day", etc). So my server function looks like this:
// This is very dumbed down for brevity
export async function getAggregateMetric(range: string) {
"use server";
await fetch(`https://plausible.io/api/v1/stats&perdiod=${range}`);
}
// This is very dumbed down for brevity
export async function getAggregateMetric(range: string) {
"use server";
await fetch(`https://plausible.io/api/v1/stats&perdiod=${range}`);
}
Here's my question. I'm using cache, createAsync() , and the load() function to render the initial data for the page. I'm setting a hard coded value for the first load. But after that I want to let users change the time period by clicking buttons. I've pasted my design pattern below. Is it a good pattern? I asking because I periodically get a page flicker or refresh when I click the buttons and I can't figure out why.
const getMetrics = cache(
async (range: string): Promise<AggregateResult | undefined> => {
"use server";
return await getAggregateMetrics(range);
},
"metrics"
);

// hard coded initial value
export const route = {
load() {
void getMetrics("6mo");
},
};

export default function AddNote() {
// hard coded initial value
const [range, setRange] = createSignal("6mo");
// using a signal here to force createAsync to rerun
const metrics = createAsync(() => getMetrics(range()));

function changeMetrics(newRange: string) {
setRange(newRange);
};

return (
<>
<h1>Metrics</h1>
<MetricsBar metrics={metrics}/>
<button onClick={() => changeMetrics("day")}>Day</button>
<button onClick={() => changeMetrics("6mo")}>6mo</button>
<button onClick={() => changeMetrics("12mo")}>12mo</button>
</>
);
}
const getMetrics = cache(
async (range: string): Promise<AggregateResult | undefined> => {
"use server";
return await getAggregateMetrics(range);
},
"metrics"
);

// hard coded initial value
export const route = {
load() {
void getMetrics("6mo");
},
};

export default function AddNote() {
// hard coded initial value
const [range, setRange] = createSignal("6mo");
// using a signal here to force createAsync to rerun
const metrics = createAsync(() => getMetrics(range()));

function changeMetrics(newRange: string) {
setRange(newRange);
};

return (
<>
<h1>Metrics</h1>
<MetricsBar metrics={metrics}/>
<button onClick={() => changeMetrics("day")}>Day</button>
<button onClick={() => changeMetrics("6mo")}>6mo</button>
<button onClick={() => changeMetrics("12mo")}>12mo</button>
</>
);
}
Thank you!
15 replies
SSolidJS
Created by ChrisThornham on 9/8/2024 in #support
Unexpected SolidStart Behavior On Vercel. Can You Help?
I have an AuthContextProvider that works on npm run dev but doesn't work on Vercel. I'm lost. Any help would be appreciated. Here's the flow: 1. I'm using a Solid store (createStore) to store auth state. 2. When the auth state changes, I update the Solid store. 3. If a user is authenticated AND they are on a page that has /auth in the pathname, I redirect the user to the app dashboard. This prevents authenticated users from accessing sign-in or sign-up pages. Everything works as expected when running npm run dev. When a user signs in on the /auth/sign-in page: 1. An auth state change occurs. 2. I set isAuthenticated to true 3. Then, I redirect the user to the app dashboard. After deploying to Vercel, the redirect stopped working. After signing in, the app stays on the /auth/sign-in page. I added a console.log to check both conditions, and both are true, but the redirect doesn't work. If I refresh my browser, the redirect occurs. Why is this happening? Does it have something to do with caching at Vercel? I'm using the latest version of SolidStart in SSR mode. Here's the code.
// Set up a store to track auth state
const defaultAuthState: SupabaseAuthData = {
isAuthenticated: false,
isLoading: true,
supabaseSession: null,
isAdmin: false,
};

const [authStore, setAuthStore] = createStore(defaultAuthState);

const { data: { subscription }, } = supabase.auth.onAuthStateChange(async (_event, session) => {
// Check to see if there is a session.
if (!!session) {
// A session exists. Update the auth store
setAuthStore({ isAuthenticated: true, supabaseSession: session, isAdmin: adminUser });

console.log(authStore.isAuthenticated); // result true
console.log(location.pathname.includes("/auth"); // result true

// NOT WORKING
if (location.pathname.includes("/auth") && authStore.isAuthenticated) {
navigate(`/${appRoute}`, { replace: true });
}
}
});
// Set up a store to track auth state
const defaultAuthState: SupabaseAuthData = {
isAuthenticated: false,
isLoading: true,
supabaseSession: null,
isAdmin: false,
};

const [authStore, setAuthStore] = createStore(defaultAuthState);

const { data: { subscription }, } = supabase.auth.onAuthStateChange(async (_event, session) => {
// Check to see if there is a session.
if (!!session) {
// A session exists. Update the auth store
setAuthStore({ isAuthenticated: true, supabaseSession: session, isAdmin: adminUser });

console.log(authStore.isAuthenticated); // result true
console.log(location.pathname.includes("/auth"); // result true

// NOT WORKING
if (location.pathname.includes("/auth") && authStore.isAuthenticated) {
navigate(`/${appRoute}`, { replace: true });
}
}
});
5 replies
SSolidJS
Created by ChrisThornham on 9/6/2024 in #support
How Can I Generate A Sitemap?
I'm days away from releasing an SSR SolidStart project that has 150+ pages. I was hoping to dynamically create a sitemap, but I'm not having much luck. I tried this: https://github.com/jbaubree/vite-plugin-pages-sitemap Unfortunately, the docs weren't super clear. I configured all of the settings (shown below), but I couldn't figure out how to generate the actual sitemap. If anyone has an easy solution for creating a sitemap, I'm all ears. Thanks!
import { defineConfig } from "@solidjs/start/config";
/* @ts-ignore */
import pkg from "@vinxi/plugin-mdx";
import Pages from "vite-plugin-pages";
import generateSitemap from "vite-plugin-pages-sitemap";

const { default: mdx } = pkg;
export default defineConfig({
ssr: true,
server: { preset: "vercel" },
extensions: ["mdx", "md"],
vite: {
plugins: [
mdx.withImports({})({
jsx: true,
jsxImportSource: "solid-js",
providerImportSource: "solid-mdx",
}),
Pages({
onRoutesGenerated: (routes) =>
generateSitemap({
routes,
hostname: "https://www.example.com/",
readable: true,
exclude: [
"/account",
"/admin-dashboard",
"/app-dashboard",
"/checkout",
"/api",
],
}),
}),
],
},
});
import { defineConfig } from "@solidjs/start/config";
/* @ts-ignore */
import pkg from "@vinxi/plugin-mdx";
import Pages from "vite-plugin-pages";
import generateSitemap from "vite-plugin-pages-sitemap";

const { default: mdx } = pkg;
export default defineConfig({
ssr: true,
server: { preset: "vercel" },
extensions: ["mdx", "md"],
vite: {
plugins: [
mdx.withImports({})({
jsx: true,
jsxImportSource: "solid-js",
providerImportSource: "solid-mdx",
}),
Pages({
onRoutesGenerated: (routes) =>
generateSitemap({
routes,
hostname: "https://www.example.com/",
readable: true,
exclude: [
"/account",
"/admin-dashboard",
"/app-dashboard",
"/checkout",
"/api",
],
}),
}),
],
},
});
3 replies
SSolidJS
Created by ChrisThornham on 9/5/2024 in #support
Environment Variables Missing `npm run start`
All of my environment variables are missing or undefined after running npm run build and npm run start. npm run dev works as expected. Does the build have access to the .env file at the root of the project? If not, how do I access them in the build?
12 replies
SSolidJS
Created by ChrisThornham on 7/31/2024 in #support
<A> Component: #id Breaking Link
I'm building a docs page for a project I'm working on. If I create a link with a fragement id pointed to a div on the same page, everything works as expected. The page scrolls to the element with the appropriate id.
|<A href="#section1">Link to section 1</A>

// One the same page
<div id="section1">...</div>
|<A href="#section1">Link to section 1</A>

// One the same page
<div id="section1">...</div>
But, if I link to another page, nothing happens. I click the link and it doesn't navigate.
|<A href="/to/another/page#section1">Link to section 1</A>

// On another page
<div id="section1">...</div>
|<A href="/to/another/page#section1">Link to section 1</A>

// On another page
<div id="section1">...</div>
Is there an easy fix for this?
4 replies
SSolidJS
Created by ChrisThornham on 7/11/2024 in #support
Can The load() Function Access Secure Values?
If I want to take advantage of load on hover when using a url Param, I can use the following pattern. Pass the param
<A href={`/${orderId}`}>Link</A>
<A href={`/${orderId}`}>Link</A>
Then in the component
const getOrder = cache(async (orderId: string): Promise<Order | undefined> => {
"use server";
return await getOrder(orderId);
}, "order");

// Get the param
export const route = {
load({ params }) {
void getOrder(params.orderId);
},
} satisfies RouteDefinition;

export default function OrderDetailsPage(props: RouteSectionProps) {
const order = createAsync(() => getOrder(props.params.orderId));
//... use the order
}
const getOrder = cache(async (orderId: string): Promise<Order | undefined> => {
"use server";
return await getOrder(orderId);
}, "order");

// Get the param
export const route = {
load({ params }) {
void getOrder(params.orderId);
},
} satisfies RouteDefinition;

export default function OrderDetailsPage(props: RouteSectionProps) {
const order = createAsync(() => getOrder(props.params.orderId));
//... use the order
}
But what if I need to pass getOrder() something secure like a userId or a jwt? Is there a way that I can pass a secure value to the load function without exposing it?
15 replies
SSolidJS
Created by ChrisThornham on 7/11/2024 in #support
Help Me Understand "use server" Security
I'm new to this topic. So please excuse me if my questions if they're obvious or don't sound well thought out. I'm simply trying to learn. Let's assume I have an app dashboard. When a user visits that dashboard, I run a createEffect() to check their access status. Inside that "client-side" createEffect() I call a server function getUserRecord() Here's a simplified example to show logic.
// Get the userId and email of logged in user
const supabaseAuthContext = useSupabaseAuthContext()!;
const userId = supabaseAuthContext.supabaseSession?.user.id;
const userEmail = supabaseAuthContext.supabaseSession?.user.email;

createEffect(async () => {
if (userId && userEmail) {
const userRecord = await getUserRecord(userId);
if (userRecord) {
if (userRecord.has_app_access) {
// let them access the app
} else {
// Navigate away
}
}
}
});
// Get the userId and email of logged in user
const supabaseAuthContext = useSupabaseAuthContext()!;
const userId = supabaseAuthContext.supabaseSession?.user.id;
const userEmail = supabaseAuthContext.supabaseSession?.user.email;

createEffect(async () => {
if (userId && userEmail) {
const userRecord = await getUserRecord(userId);
if (userRecord) {
if (userRecord.has_app_access) {
// let them access the app
} else {
// Navigate away
}
}
}
});
Here's the getUserRecord() function:
export async function getUserRecord(userId: string) {
"use server";
// Get Supabase Admin Client
const supabaseAdmin = getSupabaseAdminClient();

if (supabaseAdmin) {
// Query supabase
const { data, error } = await supabaseAdmin
.from("users")
.select()
.eq("auth_user_id", userId);

if (error) {
console.log(error.message);
}
return data;
}
}
export async function getUserRecord(userId: string) {
"use server";
// Get Supabase Admin Client
const supabaseAdmin = getSupabaseAdminClient();

if (supabaseAdmin) {
// Query supabase
const { data, error } = await supabaseAdmin
.from("users")
.select()
.eq("auth_user_id", userId);

if (error) {
console.log(error.message);
}
return data;
}
}
For context, here's the getSupabaseAdminClient() function:
export function getSupabaseAdminClient() {
"use server";

// Get the environment variables
const projectURL = process.env.SUPABASE_PROJECT_URL;
const supabaseServiceRole = process.env.SUPABASE_SERVICE_ROLE;
if (!projectURL || !supabaseServiceRole) {
// Log error
console.error("Unable to initialize Supabase Auth Admin Client");
return;
}

return createClient(projectURL, supabaseServiceRole, {
auth: {
autoRefreshToken: false,
persistSession: false,
},
});
}
export function getSupabaseAdminClient() {
"use server";

// Get the environment variables
const projectURL = process.env.SUPABASE_PROJECT_URL;
const supabaseServiceRole = process.env.SUPABASE_SERVICE_ROLE;
if (!projectURL || !supabaseServiceRole) {
// Log error
console.error("Unable to initialize Supabase Auth Admin Client");
return;
}

return createClient(projectURL, supabaseServiceRole, {
auth: {
autoRefreshToken: false,
persistSession: false,
},
});
}
50 replies
SSolidJS
Created by ChrisThornham on 7/8/2024 in #support
<Title> Component Throwing Error. Can you help?
I built an SEO component that simplifies adding SEO to pages. Unfortunately, I'm now getting the following error on every page: "seo.tsx:20 computations created outside a createRoot or render will never be disposed" The <Title> component is causing the error.
<Title>
{props.seoData.title} - {brandName}
</Title>
<Title>
{props.seoData.title} - {brandName}
</Title>
Why? Here's the full component for context:
import { SEOType } from "~/types/seo";
import { brandName, domain } from "../../../../quickstart.config";
import { Link, Meta, Title } from "@solidjs/meta";
import { Show } from "solid-js";

interface SEOProps {
seoData: SEOType;
}

export default function SEO(props: SEOProps) {
// VARIABLES ========================================================
const fullUrl = props.seoData.urlSlug
? `${domain}/blog/${props.seoData.urlSlug}`
: "";

// TSX ==============================================================
return (
<>
{/* General */}
<Title>
{props.seoData.title} - {brandName}
</Title>
<Show
when={props.seoData.index}
fallback={<Meta name="robots" content="noindex" />}
>
<Meta name="robots" content="index" />
</Show>
<Show
when={props.seoData.follow}
fallback={<Meta name="robots" content="nofollow" />}
>
<Meta name="robots" content="follow" />
</Show>
<Meta name="description" content={props.seoData.description} />

<Meta property="og:description" content={props.seoData.description} />
<Meta
property="og:title"
content={`${props.seoData.title} - ${brandName}`}
/>
<Meta property="og:type" content="website" />
<Show when={props.seoData.urlSlug}>
<Meta property="og:url" content={fullUrl} />
</Show>
<Meta property="og:site_name" content={brandName} />

<Show when={props.seoData.urlSlug}>
<Link rel="canonical" href={fullUrl} />
</Show>
</>
);
}
import { SEOType } from "~/types/seo";
import { brandName, domain } from "../../../../quickstart.config";
import { Link, Meta, Title } from "@solidjs/meta";
import { Show } from "solid-js";

interface SEOProps {
seoData: SEOType;
}

export default function SEO(props: SEOProps) {
// VARIABLES ========================================================
const fullUrl = props.seoData.urlSlug
? `${domain}/blog/${props.seoData.urlSlug}`
: "";

// TSX ==============================================================
return (
<>
{/* General */}
<Title>
{props.seoData.title} - {brandName}
</Title>
<Show
when={props.seoData.index}
fallback={<Meta name="robots" content="noindex" />}
>
<Meta name="robots" content="index" />
</Show>
<Show
when={props.seoData.follow}
fallback={<Meta name="robots" content="nofollow" />}
>
<Meta name="robots" content="follow" />
</Show>
<Meta name="description" content={props.seoData.description} />

<Meta property="og:description" content={props.seoData.description} />
<Meta
property="og:title"
content={`${props.seoData.title} - ${brandName}`}
/>
<Meta property="og:type" content="website" />
<Show when={props.seoData.urlSlug}>
<Meta property="og:url" content={fullUrl} />
</Show>
<Meta property="og:site_name" content={brandName} />

<Show when={props.seoData.urlSlug}>
<Link rel="canonical" href={fullUrl} />
</Show>
</>
);
}
7 replies
SSolidJS
Created by ChrisThornham on 7/1/2024 in #support
Is My Understanding Of RouteSectionProps Correct?
I’ve been trying to implement the cache, load, and createAsync pattern on all of my pages that load data. My goal is to load data on hover before navigating to the page. When the cache function does not need a route parameter, this pattern works as expected. BUT, I struggled to get this pattern working when I needed to pass my cache function a route param. After reading through the docs, I "thought" that I should use the useParams() primitive to get the route params, then pass that route param to the function inside of createAsync(). That didn't work. I dug through the HackerNews example and noticed the use of RouteSectionProps. With RouteSectionProps it does work. So... I want to make sure I understand WHY these two examples differ. When using useParams() I'm guessing that you only get access to the route params AFTER the component loads... meaning the params aren't available on hover. But, when using RouteSectionProps it appears that you get access to the route params BEFORE the component loads... which is why load on hover works. Am I correct? Here's my final implementation.
const getUser = cache(async (email: string): Promise<User> => {
"use server";
const { data, error } = await supabase
.from("users")
.select()
.eq("customer_email", email)
.single();

if (error) {
console.log(error.message);
throw error;
}

return data as User;
}, "user");

export const route: RouteDefinition = {
load({ params }) {
void getUser(params.email);
},
};

export default function UserDetailsPage(props: RouteSectionProps) {
const user = createAsync(() => getUser(props.params.email));
...
}
const getUser = cache(async (email: string): Promise<User> => {
"use server";
const { data, error } = await supabase
.from("users")
.select()
.eq("customer_email", email)
.single();

if (error) {
console.log(error.message);
throw error;
}

return data as User;
}, "user");

export const route: RouteDefinition = {
load({ params }) {
void getUser(params.email);
},
};

export default function UserDetailsPage(props: RouteSectionProps) {
const user = createAsync(() => getUser(props.params.email));
...
}
10 replies
SSolidJS
Created by ChrisThornham on 6/30/2024 in #support
Help Me Solve "undefined" warning When Using <Show> Component
I'm using createAsync() to get and order record.
const order = createAsync(() => getOrder(params.chargeId));
const order = createAsync(() => getOrder(params.chargeId));
order has the following type: const order: Accessor<Order | undefined> Because order could be undefined, I must check for that in my jsx. I'm trying to do that with <Show when={order()}> I thought adding the when={order()} would return false if order() was undefined, and satisfy typescript, but typescript is still complaining with this message:
Type 'Accessor<Order | undefined>' is not assignable to type 'Accessor<Order>'
Type 'Accessor<Order | undefined>' is not assignable to type 'Accessor<Order>'
So, how can I get rid of this "undefined" error? I'm trying to do this "the solid way," and I can't figure it out. I get the undefined error when trying to pass the order to a component like this: <OrderTable order={order} /> Here's the full component for context.
import { Order } from "~/types/order";
// CACHE ============================================================
const getOrder = cache(async (chargeId: string) => {
"use server";
const { data } = await supabase
.from("orders")
.select()
.eq("stripe_charge_id", chargeId)
.single();
if (!data) {
return undefined;
} else {
return data as Order;
}
}, "order");

export default function OrderDetailsPage() {
const params = useParams();
const order = createAsync(() => getOrder(params.chargeId));
return (
<>
<h4>Order Details</h4>
<Show when={order()} fallback="Order loading...">
<OrderTable order={order} />
</Show>
</>
);
}
import { Order } from "~/types/order";
// CACHE ============================================================
const getOrder = cache(async (chargeId: string) => {
"use server";
const { data } = await supabase
.from("orders")
.select()
.eq("stripe_charge_id", chargeId)
.single();
if (!data) {
return undefined;
} else {
return data as Order;
}
}, "order");

export default function OrderDetailsPage() {
const params = useParams();
const order = createAsync(() => getOrder(params.chargeId));
return (
<>
<h4>Order Details</h4>
<Show when={order()} fallback="Order loading...">
<OrderTable order={order} />
</Show>
</>
);
}
3 replies
SSolidJS
Created by ChrisThornham on 6/13/2024 in #support
What Replaced createRouteData?
In older version of SolidStart you could get all routes in a folder. This was useful for doing things like getting all blog articles. It worked like this:
export const routeData = () => {
return createRouteData(async () => {
const files = import.meta.glob('.blog/*.mdx')
})
}
export const routeData = () => {
return createRouteData(async () => {
const files = import.meta.glob('.blog/*.mdx')
})
}
But createRouteData no longer exists in the latest version of SolidStart. How could I do the same thing with the newest version of SolidStart? Thanks, Chris
5 replies
SSolidJS
Created by ChrisThornham on 6/5/2024 in #support
Is It Possible To Pass An Extra Parameter To A Form Action?
I'm trying to do this:
const userId = getUserId();

return (
<form action={deleteUser(userId) method="post">
<button>Delete User</button>
</form>
);
const userId = getUserId();

return (
<form action={deleteUser(userId) method="post">
<button>Delete User</button>
</form>
);
Here's the action:
export const deleteUser = action(async (userId: string) => {
await auth.deleteAccount(userId);
});
export const deleteUser = action(async (userId: string) => {
await auth.deleteAccount(userId);
});
I keep getting the following errors on the "action" of the form: Type 'Action<[userId: string], void>' is not assignable to type 'string | SerializableAttributeValue | undefined'.ts(2322) The expected type comes from property 'action' which is declared here on type 'FormHTMLAttributes<HTMLFormElement>' (property) JSX.FormHTMLAttributes<HTMLFormElement>.action?: string | JSX.SerializableAttributeValue | undefined
5 replies
SSolidJS
Created by ChrisThornham on 5/21/2024 in #support
Trouble With onCleanup()
I'm building a checkout flow with Stripe's embedded checkout form. It works like this. The user clicks a "Buy Now" button on a products page that navigates to a checkout page. The checkout page: - Gets the product ID and quantity from the search params. - Gets the checkout form from stripe - Mounts the checkout form. This works the first time I click a buy now button. BUT If I click back to the products page and click another buy now button, the checkout form doesn't load. I'm getting an error from Stripe: "You cannot have multiple embedded checkout items." So, how do I "clear" or unmount the checkout form when I click the back button? I want to start fresh each time I click a buy now button. My use of onCleanup (below) isn't working. Here's my checkout page:
export default function CheckoutPage() {

const [searchParams] = useSearchParams();

// ref
let checkoutElement: HTMLDivElement;

createEffect(async () => {
// extract the search params
const itemsArray = [
{
price: searchParams.priceId,
quantity: searchParams.qty,
},
];

// Stripe Stuff
// Create a checkout session
const response = await fetch("api/stripe/create-checkout-session", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
items: itemsArray,
}),
});

// Create checkout form
const { clientSecret } = await response.json();
const checkout = await stripe?.initEmbeddedCheckout({
clientSecret,
});

// Mount form
if (checkoutElement) {
checkout?.mount(checkoutElement);
}

// unmount the form
onCleanup(() => {
checkout?.unmount();
});
});

return (
<MainLayout>
<div
id="checkout"
ref={(el) => checkoutElement = el}
class="px-6 py-20 bg-[#0f151d]"
>
{/* Checkout will insert payment form here */}
</div>
</MainLayout>
);
}
export default function CheckoutPage() {

const [searchParams] = useSearchParams();

// ref
let checkoutElement: HTMLDivElement;

createEffect(async () => {
// extract the search params
const itemsArray = [
{
price: searchParams.priceId,
quantity: searchParams.qty,
},
];

// Stripe Stuff
// Create a checkout session
const response = await fetch("api/stripe/create-checkout-session", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
items: itemsArray,
}),
});

// Create checkout form
const { clientSecret } = await response.json();
const checkout = await stripe?.initEmbeddedCheckout({
clientSecret,
});

// Mount form
if (checkoutElement) {
checkout?.mount(checkoutElement);
}

// unmount the form
onCleanup(() => {
checkout?.unmount();
});
});

return (
<MainLayout>
<div
id="checkout"
ref={(el) => checkoutElement = el}
class="px-6 py-20 bg-[#0f151d]"
>
{/* Checkout will insert payment form here */}
</div>
</MainLayout>
);
}
30 replies