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: 0 additions & 4 deletions docs/start/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -314,10 +314,6 @@
{
"label": "solid",
"children": [
{
"label": "Bare",
"to": "framework/solid/examples/start-bare"
},
{
"label": "Basic",
"to": "framework/solid/examples/start-basic"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "tanstack-solid-start-example-bare",
"name": "tanstack-solid-start-example-counter",
"private": true,
"sideEffects": false,
"type": "module",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "tanstack-solid-start-streaming-data-from-server-functions",
"private": true,
"sideEffects": false,
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build && tsc --noEmit",
"start": "vite start"
Comment on lines +7 to +9
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 preview script.

Vite exposes dev, build, and preview; there is no vite start, so npm run start will fail at runtime. Switch to vite preview to align with the documented CLI.(vite.dev)

-    "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-streaming-data-from-server-functions/package.json around
lines 7 to 9, the "start" script is using the non-existent "vite start" CLI
which will fail; update the "start" script to use "vite preview" (e.g., "start":
"vite preview") so npm run start runs Vite's production preview, keeping the
existing "build" and "dev" scripts unchanged.

},
"dependencies": {
"@tanstack/solid-router": "^1.135.2",
"@tanstack/solid-router-devtools": "^1.135.2",
"@tanstack/solid-start": "^1.135.2",
"solid-js": "^1.9.10",
"zod": "^3.24.2"
},
Comment on lines +12 to +17
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 protocol for in-repo packages.

The @TanStack packages are part of this monorepo, so the dependencies here should point to workspace:* instead of published semver ranges. This keeps the example pinned to the local sources during development and satisfies the repo’s package.json guideline. As per coding guidelines.

-    "@tanstack/solid-router": "^1.135.2",
-    "@tanstack/solid-router-devtools": "^1.135.2",
-    "@tanstack/solid-start": "^1.135.2",
+    "@tanstack/solid-router": "workspace:*",
+    "@tanstack/solid-router-devtools": "workspace:*",
+    "@tanstack/solid-start": "workspace:*",
📝 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.135.2",
"@tanstack/solid-router-devtools": "^1.135.2",
"@tanstack/solid-start": "^1.135.2",
"solid-js": "^1.9.10",
"zod": "^3.24.2"
},
"@tanstack/solid-router": "workspace:*",
"@tanstack/solid-router-devtools": "workspace:*",
"@tanstack/solid-start": "workspace:*",
"solid-js": "^1.9.10",
"zod": "^3.24.2"
},
🤖 Prompt for AI Agents
In examples/solid/start-streaming-data-from-server-functions/package.json around
lines 12 to 17, the @tanstack dependencies use published semver ranges; replace
each @tanstack/* entry to use the workspace protocol (e.g., "workspace:*") so
the package points to the in-repo packages instead of external releases, and
keep other deps (solid-js, zod) unchanged.

"devDependencies": {
"@types/node": "^22.5.4",
"vite-plugin-solid": "^2.11.10",
"typescript": "^5.7.2",
"vite": "^7.1.7",
"vite-tsconfig-paths": "^5.1.4"
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/* eslint-disable */

// @ts-nocheck

// noinspection JSUnusedGlobalSymbols

// This file was automatically generated by TanStack Router.
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.

import { Route as rootRouteImport } from './routes/__root'
import { Route as IndexRouteImport } from './routes/index'

const IndexRoute = IndexRouteImport.update({
id: '/',
path: '/',
getParentRoute: () => rootRouteImport,
} as any)

export interface FileRoutesByFullPath {
'/': typeof IndexRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/'
fileRoutesByTo: FileRoutesByTo
to: '/'
id: '__root__' | '/'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
}

declare module '@tanstack/solid-router' {
interface FileRoutesByPath {
'/': {
id: '/'
path: '/'
fullPath: '/'
preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport
}
}
}

const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>()

import type { getRouter } from './router.tsx'
import type { createStart } from '@tanstack/solid-start'
declare module '@tanstack/solid-start' {
interface Register {
ssr: true
router: Awaited<ReturnType<typeof getRouter>>
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { createRouter } from '@tanstack/solid-router'
import { routeTree } from './routeTree.gen'

export function getRouter() {
const router = createRouter({
routeTree,
defaultPreload: 'intent',
defaultErrorComponent: (err) => <p>{err.error.stack}</p>,
defaultNotFoundComponent: () => <p>not found</p>,
scrollRestoration: true,
})

return router
}

declare module '@tanstack/solid-router' {
interface Register {
router: ReturnType<typeof getRouter>
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/// <reference types="vite/client" />
import { TanStackRouterDevtools } from '@tanstack/solid-router-devtools'
import {
HeadContent,
Outlet,
Scripts,
createRootRoute,
} from '@tanstack/solid-router'
import { HydrationScript } from 'solid-js/web'
import type { JSX } from 'solid-js'
import appCss from '~/styles/app.css?url'

export const Route = createRootRoute({
head: () => ({
links: [{ rel: 'stylesheet', href: appCss }],
}),
component: RootComponent,
})

function RootComponent() {
return (
<RootDocument>
<Outlet />
</RootDocument>
)
}

function RootDocument({ children }: { children: JSX.Element }) {
return (
<html>
<head>
<HydrationScript />
</head>
<body>
<HeadContent />
{children}
<TanStackRouterDevtools position="bottom-right" />
Comment on lines +31 to +37
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

Move HeadContent into <head>.

HeadContent renders <title>, meta tags, and links; keeping it in <body> produces invalid markup and prevents browsers from treating those tags as head elements. Render it inside <head> alongside HydrationScript, and leave <body> for app content/devtools.

-    <html>
-      <head>
-        <HydrationScript />
-      </head>
-      <body>
-        <HeadContent />
+    <html>
+      <head>
+        <HydrationScript />
+        <HeadContent />
+      </head>
+      <body>
         {children}
📝 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
<head>
<HydrationScript />
</head>
<body>
<HeadContent />
{children}
<TanStackRouterDevtools position="bottom-right" />
<head>
<HydrationScript />
<HeadContent />
</head>
<body>
{children}
<TanStackRouterDevtools position="bottom-right" />
🤖 Prompt for AI Agents
In
examples/solid/start-streaming-data-from-server-functions/src/routes/__root.tsx
around lines 31 to 37, HeadContent is being rendered inside the <body> which
places <title>, meta and link tags in the body; move the <HeadContent />
component into the <head> section next to <HydrationScript /> so those tags are
output inside the document head and leave the <body> to contain {children} and
the TanStackRouterDevtools.

<Scripts />
</body>
</html>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { createFileRoute } from '@tanstack/solid-router'
import { createServerFn } from '@tanstack/solid-start'
import { createSignal } from 'solid-js'
import { z } from 'zod'

/**
This schema will be used to define the type
of each chunk in the `ReadableStream`.
(It mimics OpenAI's streaming response format.)
*/
const textPartSchema = z.object({
choices: z.array(
z.object({
delta: z.object({
content: z.string().optional(),
}),
index: z.number(),
finish_reason: z.string().nullable(),
}),
),
})

export type TextPart = z.infer<typeof textPartSchema>

/**
This helper function generates the array of messages
that we'll stream to the client.
*/
function generateMessages() {
const messages = Array.from({ length: 10 }, () =>
Math.floor(Math.random() * 100),
).map((n, i) =>
textPartSchema.parse({
choices: [
{
delta: { content: `Number #${i + 1}: ${n}\n` },
index: i,
finish_reason: null,
},
],
}),
)
return messages
}

/**
This helper function is used to simulate the
delay between each message being sent.
*/
function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms))
}

/**
This server function returns a `ReadableStream`
that streams `TextPart` chunks to the client.
*/
const streamingResponseFn = createServerFn().handler(async () => {
const messages = generateMessages()
// This `ReadableStream` is typed, so each
// will be of type `TextPart`.
const stream = new ReadableStream<TextPart>({
async start(controller) {
for (const message of messages) {
// simulate network latency
await sleep(500)
controller.enqueue(message)
}
controller.close()
},
})

return stream
})

/**
You can also use an async generator function to stream
typed chunks to the client.
*/
const streamingWithAnAsyncGeneratorFn = createServerFn().handler(
async function* () {
const messages = generateMessages()
for (const msg of messages) {
await sleep(500)
// The streamed chunks are still typed as `TextPart`
yield msg
}
},
)

export const Route = createFileRoute('/')({
component: RouteComponent,
})

function RouteComponent() {
const [readableStreamMessages, setReadableStreamMessages] = createSignal('')

const [asyncGeneratorFuncMessages, setAsyncGeneratorFuncMessages] =
createSignal('')

const getTypedReadableStreamResponse = async () => {
const response = await streamingResponseFn()

if (!response) {
return
}

const reader = response.getReader()
let done = false
setReadableStreamMessages('')
while (!done) {
const { value, done: doneReading } = await reader.read()
done = doneReading
if (value) {
// Notice how we know the value of `chunk` (`TextPart | undefined`)
// here, because it's coming from the typed `ReadableStream`
const chunk = value?.choices[0].delta.content
if (chunk) {
setReadableStreamMessages((prev) => prev + chunk)
}
}
}
}

const getResponseFromTheAsyncGenerator = async () => {
setAsyncGeneratorFuncMessages('')
for await (const msg of await streamingWithAnAsyncGeneratorFn()) {
const chunk = msg?.choices[0].delta.content
if (chunk) {
setAsyncGeneratorFuncMessages((prev) => prev + chunk)
}
}
}

return (
<main>
<h1>Typed Readable Stream</h1>
<div id="streamed-results">
<button onClick={() => getTypedReadableStreamResponse()}>
Get 10 random numbers (ReadableStream)
</button>
<button onClick={() => getResponseFromTheAsyncGenerator()}>
Get 10 random numbers (Async Generator Function)
</button>
<pre>{readableStreamMessages()}</pre>
<pre>{asyncGeneratorFuncMessages()}</pre>
</div>
</main>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
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;
}

#streamed-results {
display: grid;
grid-template-columns: 1fr 1fr;
}

#streamed-results > button {
margin: auto;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"include": ["**/*.ts", "**/*.tsx", "public/script*.js"],
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"jsx": "preserve",
"jsxImportSource": "solid-js",
"module": "ESNext",
"moduleResolution": "Bundler",
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"isolatedModules": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"target": "ES2022",
"allowJs": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"paths": {
"~/*": ["./src/*"]
},
"noEmit": true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { tanstackStart } from '@tanstack/solid-start/plugin/vite'
import { defineConfig } from 'vite'
import tsConfigPaths from 'vite-tsconfig-paths'
import viteSolid from 'vite-plugin-solid'

export default defineConfig({
server: {
port: 3000,
},
plugins: [
tsConfigPaths({
projects: ['./tsconfig.json'],
}),
tanstackStart(),
viteSolid({ ssr: true }),
],
})
Loading
Loading