Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/start/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,10 @@
"label": "Basic + Static rendering",
"to": "framework/solid/examples/start-basic-static"
},
{
"label": "Basic + Supabase",
"to": "framework/solid/examples/start-basic-supabase"
},
{
"label": "Cloudflare Vite Plugin",
"to": "framework/solid/examples/start-basic-cloudflare"
Expand Down
2 changes: 2 additions & 0 deletions examples/solid/start-basic-supabase/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SUPABASE_URL=PleaseChangeMe
SUPABASE_ANON_KEY=PleaseChangeMe
18 changes: 18 additions & 0 deletions examples/solid/start-basic-supabase/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
node_modules
package-lock.json
yarn.lock

.DS_Store
.cache
.env
.vercel
.output
/build/
/api/
/server/build
/public/build# Sentry Config File
.env.sentry-build-plugin
Comment on lines +13 to +14
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix the comment placement on line 15.

The comment # Sentry Config File is appended to the end of /public/build on line 15, which breaks readability and may cause parsing issues. Move the comment to its own line above .env.sentry-build-plugin.

-/public/build# Sentry Config File
+/public/build
+# Sentry Config File
 .env.sentry-build-plugin
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/public/build# Sentry Config File
.env.sentry-build-plugin
/public/build
# Sentry Config File
.env.sentry-build-plugin
🤖 Prompt for AI Agents
In examples/solid/start-supabase-basic/.gitignore around lines 15 to 16, the
comment "# Sentry Config File" is currently appended to the end of the
"/public/build" entry which reduces readability and can confuse parsers; move
the comment onto its own line immediately above the ".env.sentry-build-plugin"
entry so "/public/build" is one line by itself and the Sentry comment sits alone
on the previous line.

/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
4 changes: 4 additions & 0 deletions examples/solid/start-basic-supabase/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
**/build
**/public
pnpm-lock.yaml
routeTree.gen.ts
11 changes: 11 additions & 0 deletions examples/solid/start-basic-supabase/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"files.watcherExclude": {
"**/routeTree.gen.ts": true
},
"search.exclude": {
"**/routeTree.gen.ts": true
},
"files.readonlyInclude": {
"**/routeTree.gen.ts": true
}
}
33 changes: 33 additions & 0 deletions examples/solid/start-basic-supabase/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "tanstack-solid-start-example-basic-supabase",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build && tsc --noEmit",
"start": "vite start"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix the production start script.

Line 10 runs vite start, but Vite doesn't ship that command; the script fails immediately. Switch to the supported preview command so npm run start works.

Apply this diff:

   "scripts": {
     "dev": "vite dev",
     "build": "vite build && tsc --noEmit",
-    "start": "vite start"
+    "start": "vite preview"
   },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"start": "vite start"
"start": "vite preview"
🤖 Prompt for AI Agents
In examples/solid/start-basic-supabase/package.json around line 10, the "start"
script incorrectly uses "vite start" which is not a valid Vite command; update
the script value to use the supported preview command by replacing "vite start"
with "vite preview" so that npm run start works in production.

},
Comment on lines +8 to +11
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix the production start script.
Line 10 runs vite start, but Vite doesn’t ship that command; the script fails immediately. Switch to the supported preview command so npm run start works.

Apply this diff:

   "scripts": {
     "dev": "vite dev",
     "build": "vite build && tsc --noEmit",
-    "start": "vite start"
+    "start": "vite preview"
   },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"dev": "vite dev",
"build": "vite build && tsc --noEmit",
"start": "vite start"
},
"dev": "vite dev",
"build": "vite build && tsc --noEmit",
"start": "vite preview"
},
🤖 Prompt for AI Agents
In examples/solid/start-supabase-basic/package.json around lines 8 to 11, the
"start" script incorrectly uses "vite start" which is not a valid Vite command;
replace it with the supported production preview command "vite preview" so npm
run start will run the Vite preview server for built assets.

"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@supabase/ssr": "^0.5.2",
"@supabase/supabase-js": "^2.48.1",
"@tanstack/solid-router": "^1.133.25",
"@tanstack/solid-router-devtools": "^1.133.25",
"@tanstack/solid-start": "^1.133.26",
Comment on lines +18 to +20
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use workspace ranges for internal TanStack packages.

Lines 18–20 pin internal packages to public versions, ignoring the repo guideline to consume them via the workspace linker. Update these entries to workspace:* so examples test against the local sources.

Apply this diff:

   "dependencies": {
     "@supabase/ssr": "^0.5.2",
     "@supabase/supabase-js": "^2.48.1",
-    "@tanstack/solid-router": "^1.133.25",
-    "@tanstack/solid-router-devtools": "^1.133.25",
-    "@tanstack/solid-start": "^1.133.26",
+    "@tanstack/solid-router": "workspace:*",
+    "@tanstack/solid-router-devtools": "workspace:*",
+    "@tanstack/solid-start": "workspace:*",
     "solid-js": "^1.9.9",
     "redaxios": "^0.5.1"
   },

As per coding guidelines.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"@tanstack/solid-router": "^1.133.25",
"@tanstack/solid-router-devtools": "^1.133.25",
"@tanstack/solid-start": "^1.133.26",
"@tanstack/solid-router": "workspace:*",
"@tanstack/solid-router-devtools": "workspace:*",
"@tanstack/solid-start": "workspace:*",
🤖 Prompt for AI Agents
In examples/solid/start-basic-supabase/package.json around lines 18 to 20, the
three internal TanStack dependencies are pinned to public versions; change each
of "@tanstack/solid-router", "@tanstack/solid-router-devtools", and
"@tanstack/solid-start" to use the workspace range (workspace:*) so the example
consumes local workspace packages instead of the published versions, then run
npm/yarn install to update the lockfile.

"solid-js": "^1.9.9",
Comment on lines +16 to +21
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Use workspace ranges for internal TanStack packages.
Lines 16-21 pin internal packages to public versions, ignoring the repo guideline to consume them via the workspace linker. Update these entries to workspace:* so examples test against the local sources.
[As per coding guidelines]

Apply this diff:

   "dependencies": {
     "@supabase/ssr": "^0.5.2",
     "@supabase/supabase-js": "^2.48.1",
-    "@tanstack/solid-router": "^1.133.25",
-    "@tanstack/solid-router-devtools": "^1.133.25",
-    "@tanstack/solid-start": "^1.133.26",
+    "@tanstack/solid-router": "workspace:*",
+    "@tanstack/solid-router-devtools": "workspace:*",
+    "@tanstack/solid-start": "workspace:*",
     "solid-js": "^1.9.9",
     "redaxios": "^0.5.1"
   },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"@supabase/ssr": "^0.5.2",
"@supabase/supabase-js": "^2.48.1",
"@tanstack/solid-router": "^1.133.25",
"@tanstack/solid-router-devtools": "^1.133.25",
"@tanstack/solid-start": "^1.133.26",
"solid-js": "^1.9.9",
"@supabase/ssr": "^0.5.2",
"@supabase/supabase-js": "^2.48.1",
"@tanstack/solid-router": "workspace:*",
"@tanstack/solid-router-devtools": "workspace:*",
"@tanstack/solid-start": "workspace:*",
"solid-js": "^1.9.9",
🤖 Prompt for AI Agents
In examples/solid/start-supabase-basic/package.json around lines 16 to 21, the
internal TanStack packages are pinned to public versions; update the @tanstack/*
entries to use the workspace linker by replacing their version strings with
"workspace:*" (specifically for @tanstack/solid-router,
@tanstack/solid-router-devtools, and @tanstack/solid-start) so the example
consumes local sources per repo guidelines; leave third-party deps (e.g.,
@supabase/*, solid-js) unchanged.

"redaxios": "^0.5.1"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.15",
"@types/node": "^22.5.4",
"tailwindcss": "^4.1.6",
"typescript": "^5.7.2",
"vite": "^7.1.7",
"vite-plugin-solid": "^2.11.10",
"vite-tsconfig-paths": "^5.1.4"
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
19 changes: 19 additions & 0 deletions examples/solid/start-basic-supabase/public/site.webmanifest
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "",
"short_name": "",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}
54 changes: 54 additions & 0 deletions examples/solid/start-basic-supabase/src/components/Auth.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type * as Solid from 'solid-js'

export function Auth(props: {
actionText: string
onSubmit: (e: Event) => void
status: 'pending' | 'idle' | 'success' | 'error'
afterSubmit?: Solid.JSX.Element
}) {
return (
<div class="fixed inset-0 bg-white dark:bg-black flex items-start justify-center p-8">
<div class="bg-white dark:bg-gray-900 p-8 rounded-lg shadow-lg">
<h1 class="text-2xl font-bold mb-4">{props.actionText}</h1>
<form
onSubmit={(e) => {
e.preventDefault()
props.onSubmit(e)
}}
class="space-y-4"
>
<div>
<label for="email" class="block text-xs">
Username
</label>
<input
type="email"
name="email"
id="email"
class="px-2 py-1 w-full rounded border border-gray-500/20 bg-white dark:bg-gray-800"
/>
Comment on lines +21 to +29
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix label text to match input type

The label displays "Username" but the input has type="email", creating a mismatch between the label and the expected input format.

Apply this diff:

             <label for="email" class="block text-xs">
-              Username
+              Email
             </label>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<label for="email" class="block text-xs">
Username
</label>
<input
type="email"
name="email"
id="email"
class="px-2 py-1 w-full rounded border border-gray-500/20 bg-white dark:bg-gray-800"
/>
<label for="email" class="block text-xs">
Email
</label>
<input
type="email"
name="email"
id="email"
class="px-2 py-1 w-full rounded border border-gray-500/20 bg-white dark:bg-gray-800"
/>
🤖 Prompt for AI Agents
In examples/solid/start-basic-supabase/src/components/Auth.tsx around lines 21
to 29, the label reads "Username" while the input is type="email"; update the
label text to "Email" so it matches the input type and intent (ensure the
label's for attribute remains "email" to preserve accessibility).

</div>
<div>
<label for="password" class="block text-xs">
Password
</label>
<input
type="password"
name="password"
id="password"
class="px-2 py-1 w-full rounded border border-gray-500/20 bg-white dark:bg-gray-800"
/>
</div>
<button
type="submit"
class="w-full bg-cyan-600 text-white rounded py-2 font-black uppercase"
disabled={props.status === 'pending'}
>
{props.status === 'pending' ? '...' : props.actionText}
</button>
{props.afterSubmit}
</form>
</div>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {
ErrorComponent,
Link,
rootRouteId,
useMatch,
useRouter,
} from '@tanstack/solid-router'
import type { ErrorComponentProps } from '@tanstack/solid-router'

export function DefaultCatchBoundary({ error }: ErrorComponentProps) {
const router = useRouter()
const isRoot = useMatch({
strict: false,
select: (state) => state.id === rootRouteId,
})

console.error(error)

return (
<div class="min-w-0 flex-1 p-4 flex flex-col items-center justify-center gap-6">
<ErrorComponent error={error} />
<div class="flex gap-2 items-center flex-wrap">
<button
onClick={() => {
router.invalidate()
}}
class={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded text-white uppercase font-extrabold`}
>
Try Again
</button>
{isRoot() ? (
<Link
to="/"
class={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded text-white uppercase font-extrabold`}
>
Home
</Link>
) : (
<Link
to="/"
class={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded text-white uppercase font-extrabold`}
onClick={(e) => {
e.preventDefault()
window.history.back()
}}
>
Go Back
</Link>
)}
</div>
</div>
)
}
72 changes: 72 additions & 0 deletions examples/solid/start-basic-supabase/src/components/Login.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { useRouter } from '@tanstack/solid-router'
import { useServerFn } from '@tanstack/solid-start'
import { useMutation } from '../hooks/useMutation'
import { loginFn } from '../routes/_authed'
import { signupFn } from '../routes/signup'
import { Auth } from './Auth'

export function Login() {
const router = useRouter()

const loginMutation = useMutation({
fn: loginFn,
onSuccess: async (ctx) => {
if (!ctx.data?.error) {
await router.invalidate()
router.navigate({ to: '/' })
return
}
},
})
Comment on lines +11 to +20
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Use useServerFn(loginFn) on the client for consistency and SSR boundaries

Calling a server function from a client component should go through useServerFn to avoid bundling/server boundary pitfalls. You already do this for signupFn. Align loginFn too.

Apply this diff:

-  const loginMutation = useMutation({
-    fn: loginFn,
+  const loginMutation = useMutation({
+    fn: useServerFn(loginFn),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const loginMutation = useMutation({
fn: loginFn,
onSuccess: async (ctx) => {
if (!ctx.data?.error) {
await router.invalidate()
router.navigate({ to: '/' })
return
}
},
})
const loginMutation = useMutation({
fn: useServerFn(loginFn),
onSuccess: async (ctx) => {
if (!ctx.data?.error) {
await router.invalidate()
router.navigate({ to: '/' })
return
}
},
})
🤖 Prompt for AI Agents
In examples/solid/start-supabase-basic/src/components/Login.tsx around lines 11
to 20, the login mutation calls the server function directly (loginFn) from a
client component; change it to use useServerFn(loginFn) like signup to preserve
SSR boundaries and avoid bundling server code into the client. Replace direct
references to loginFn with the handler returned by useServerFn, pass that
handler into your mutation fn, and ensure any imports/typing reflect useServerFn
usage so the client only calls the server bridge rather than bundling the server
function.


const signupMutation = useMutation({
fn: useServerFn(signupFn),
})

return (
<Auth
actionText="Login"
status={loginMutation.status()}
onSubmit={(e) => {
const formData = new FormData(e.target as HTMLFormElement)

loginMutation.mutate({
data: {
email: formData.get('email') as string,
password: formData.get('password') as string,
},
})
}}
afterSubmit={
loginMutation.data() ? (
<>
<div class="text-red-400">{loginMutation.data()?.message}</div>
{loginMutation.data()?.error &&
loginMutation.data()?.message === 'Invalid login credentials' ? (
<div>
<button
class="text-blue-500"
onClick={(e) => {
const formData = new FormData(
(e.target as HTMLButtonElement).form!,
)

signupMutation.mutate({
data: {
email: formData.get('email') as string,
password: formData.get('password') as string,
},
})
}}
type="button"
>
Sign up instead?
</button>
</div>
) : null}
</>
) : null
}
/>
)
}
26 changes: 26 additions & 0 deletions examples/solid/start-basic-supabase/src/components/NotFound.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Link } from '@tanstack/solid-router'
import type * as Solid from 'solid-js'

export function NotFound({ children }: { children?: Solid.JSX.Element }) {
return (
<div class="space-y-2 p-2">
<div class="text-gray-600 dark:text-gray-400">
{children || <p>The page you are looking for does not exist.</p>}
</div>
<p class="flex items-center gap-2 flex-wrap">
<button
onClick={() => window.history.back()}
class="bg-emerald-500 text-white px-2 py-1 rounded uppercase font-black text-sm"
>
Go back
</button>
<Link
to="/"
class="bg-cyan-600 text-white px-2 py-1 rounded uppercase font-black text-sm"
>
Start Over
</Link>
</p>
</div>
)
}
42 changes: 42 additions & 0 deletions examples/solid/start-basic-supabase/src/hooks/useMutation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { createSignal } from 'solid-js'

export function useMutation<TVariables, TData, TError = Error>(opts: {
fn: (variables: TVariables) => Promise<TData>
onSuccess?: (ctx: { data: TData }) => void | Promise<void>
}) {
const [submittedAt, setSubmittedAt] = createSignal<number | undefined>()
const [variables, setVariables] = createSignal<TVariables | undefined>()
const [error, setError] = createSignal<TError | undefined>()
const [data, setData] = createSignal<TData | undefined>()
const [status, setStatus] = createSignal<
'idle' | 'pending' | 'success' | 'error'
>('idle')

const mutate = async (variables: TVariables): Promise<TData | undefined> => {
setStatus('pending')
setSubmittedAt(Date.now())
setVariables(() => variables)

try {
const result = await opts.fn(variables)
await opts.onSuccess?.({ data: result })
setStatus('success')
setError(undefined)
setData(() => result)
return result
} catch (err) {
setStatus('error')
setError(() => err as TError)
return undefined
}
Comment on lines +15 to +31
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Prevent stale data and clear state when re-submitting

On error, data remains from a previous success. Also clear last error/data when entering pending.

Apply this diff:

   const mutate = async (variables: TVariables): Promise<TData | undefined> => {
-    setStatus('pending')
+    setStatus('pending')
     setSubmittedAt(Date.now())
     setVariables(() => variables)
+    setError(undefined)
+    setData(undefined)

     try {
-      const result = await opts.fn(variables)
-      await opts.onSuccess?.({ data: result })
+      const result = await opts.fn(variables)
+      // Set data before onSuccess so UI can react even if onSuccess throws
+      setData(() => result)
+      await opts.onSuccess?.({ data: result })
       setStatus('success')
-      setError(undefined)
-      setData(() => result)
       return result
     } catch (err) {
       setStatus('error')
       setError(() => err as TError)
+      setData(undefined)
       return undefined
     }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const mutate = async (variables: TVariables): Promise<TData | undefined> => {
setStatus('pending')
setSubmittedAt(Date.now())
setVariables(() => variables)
try {
const result = await opts.fn(variables)
await opts.onSuccess?.({ data: result })
setStatus('success')
setError(undefined)
setData(() => result)
return result
} catch (err) {
setStatus('error')
setError(() => err as TError)
return undefined
}
const mutate = async (variables: TVariables): Promise<TData | undefined> => {
setStatus('pending')
setSubmittedAt(Date.now())
setVariables(() => variables)
setError(undefined)
setData(undefined)
try {
const result = await opts.fn(variables)
// Set data before onSuccess so UI can react even if onSuccess throws
setData(() => result)
await opts.onSuccess?.({ data: result })
setStatus('success')
return result
} catch (err) {
setStatus('error')
setError(() => err as TError)
setData(undefined)
return undefined
}
}
🤖 Prompt for AI Agents
In examples/solid/start-supabase-basic/src/hooks/useMutation.ts around lines
15–31, the mutate function leaves previous success data when a new request is
started and doesn't clear state on error; before calling opts.fn in the pending
branch clear prior error and data by calling setError(undefined) and setData(()
=> undefined) (or setData(undefined)), and in the catch block ensure you also
clear data (e.g., setData(() => undefined)) while setting the error and status
to 'error' so stale successful results are not returned after failures.

}

return {
status,
variables,
submittedAt,
mutate,
error,
data,
}
}
Loading
Loading