diff --git a/examples/react/start-basic-static/.gitignore b/examples/react/start-basic-static/.gitignore
new file mode 100644
index 0000000000..be342025da
--- /dev/null
+++ b/examples/react/start-basic-static/.gitignore
@@ -0,0 +1,22 @@
+node_modules
+package-lock.json
+yarn.lock
+
+.DS_Store
+.cache
+.env
+.vercel
+.output
+.vinxi
+
+/build/
+/api/
+/server/build
+/public/build
+.vinxi
+# Sentry Config File
+.env.sentry-build-plugin
+/test-results/
+/playwright-report/
+/blob-report/
+/playwright/.cache/
diff --git a/examples/react/start-basic-static/.prettierignore b/examples/react/start-basic-static/.prettierignore
new file mode 100644
index 0000000000..2be5eaa6ec
--- /dev/null
+++ b/examples/react/start-basic-static/.prettierignore
@@ -0,0 +1,4 @@
+**/build
+**/public
+pnpm-lock.yaml
+routeTree.gen.ts
\ No newline at end of file
diff --git a/examples/react/start-basic-static/README.md b/examples/react/start-basic-static/README.md
new file mode 100644
index 0000000000..eb580a5bf8
--- /dev/null
+++ b/examples/react/start-basic-static/README.md
@@ -0,0 +1,72 @@
+# Welcome to TanStack.com!
+
+This site is built with TanStack Router!
+
+- [TanStack Router Docs](https://tanstack.com/router)
+
+It's deployed automagically with Vercel!
+
+- [Vercel](https://vercel.com/)
+
+## Development
+
+From your terminal:
+
+```sh
+pnpm install
+pnpm dev
+```
+
+This starts your app in development mode, rebuilding assets on file changes.
+
+## Editing and previewing the docs of TanStack projects locally
+
+The documentations for all TanStack projects except for `React Charts` are hosted on [https://tanstack.com](https://tanstack.com), powered by this TanStack Router app.
+In production, the markdown doc pages are fetched from the GitHub repos of the projects, but in development they are read from the local file system.
+
+Follow these steps if you want to edit the doc pages of a project (in these steps we'll assume it's [`TanStack/form`](https://github.com/tanstack/form)) and preview them locally :
+
+1. Create a new directory called `tanstack`.
+
+```sh
+mkdir tanstack
+```
+
+2. Enter the directory and clone this repo and the repo of the project there.
+
+```sh
+cd tanstack
+git clone git@github.com:TanStack/tanstack.com.git
+git clone git@github.com:TanStack/form.git
+```
+
+> [!NOTE]
+> Your `tanstack` directory should look like this:
+>
+> ```
+> tanstack/
+> |
+> +-- form/
+> |
+> +-- tanstack.com/
+> ```
+
+> [!WARNING]
+> Make sure the name of the directory in your local file system matches the name of the project's repo. For example, `tanstack/form` must be cloned into `form` (this is the default) instead of `some-other-name`, because that way, the doc pages won't be found.
+
+3. Enter the `tanstack/tanstack.com` directory, install the dependencies and run the app in dev mode:
+
+```sh
+cd tanstack.com
+pnpm i
+# The app will run on https://localhost:3000 by default
+pnpm dev
+```
+
+4. Now you can visit http://localhost:3000/form/latest/docs/overview in the browser and see the changes you make in `tanstack/form/docs`.
+
+> [!NOTE]
+> The updated pages need to be manually reloaded in the browser.
+
+> [!WARNING]
+> You will need to update the `docs/config.json` file (in the project's repo) if you add a new doc page!
diff --git a/examples/react/start-basic-static/app.config.ts b/examples/react/start-basic-static/app.config.ts
new file mode 100644
index 0000000000..78c6c550a6
--- /dev/null
+++ b/examples/react/start-basic-static/app.config.ts
@@ -0,0 +1,19 @@
+import { defineConfig } from '@tanstack/start/config'
+import tsConfigPaths from 'vite-tsconfig-paths'
+
+export default defineConfig({
+ vite: {
+ plugins: [
+ tsConfigPaths({
+ projects: ['./tsconfig.json'],
+ }),
+ ],
+ },
+ server: {
+ preset: 'netlify',
+ prerender: {
+ routes: ['/'],
+ crawlLinks: true,
+ },
+ },
+})
diff --git a/examples/react/start-basic-static/app/client.tsx b/examples/react/start-basic-static/app/client.tsx
new file mode 100644
index 0000000000..b14d8aac68
--- /dev/null
+++ b/examples/react/start-basic-static/app/client.tsx
@@ -0,0 +1,8 @@
+///
+import { hydrateRoot } from 'react-dom/client'
+import { StartClient } from '@tanstack/start'
+import { createRouter } from './router'
+
+const router = createRouter()
+
+hydrateRoot(document, )
diff --git a/examples/react/start-basic-static/app/components/DefaultCatchBoundary.tsx b/examples/react/start-basic-static/app/components/DefaultCatchBoundary.tsx
new file mode 100644
index 0000000000..f750e7bd2b
--- /dev/null
+++ b/examples/react/start-basic-static/app/components/DefaultCatchBoundary.tsx
@@ -0,0 +1,53 @@
+import {
+ ErrorComponent,
+ Link,
+ rootRouteId,
+ useMatch,
+ useRouter,
+} from '@tanstack/react-router'
+import type { ErrorComponentProps } from '@tanstack/react-router'
+
+export function DefaultCatchBoundary({ error }: ErrorComponentProps) {
+ const router = useRouter()
+ const isRoot = useMatch({
+ strict: false,
+ select: (state) => state.id === rootRouteId,
+ })
+
+ console.error('DefaultCatchBoundary Error:', error)
+
+ return (
+
+
+
+ {
+ router.invalidate()
+ }}
+ className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded text-white uppercase font-extrabold`}
+ >
+ Try Again
+
+ {isRoot ? (
+
+ Home
+
+ ) : (
+ {
+ e.preventDefault()
+ window.history.back()
+ }}
+ >
+ Go Back
+
+ )}
+
+
+ )
+}
diff --git a/examples/react/start-basic-static/app/components/NotFound.tsx b/examples/react/start-basic-static/app/components/NotFound.tsx
new file mode 100644
index 0000000000..7b54fa5680
--- /dev/null
+++ b/examples/react/start-basic-static/app/components/NotFound.tsx
@@ -0,0 +1,25 @@
+import { Link } from '@tanstack/react-router'
+
+export function NotFound({ children }: { children?: any }) {
+ return (
+
+
+ {children ||
The page you are looking for does not exist.
}
+
+
+ window.history.back()}
+ className="bg-emerald-500 text-white px-2 py-1 rounded uppercase font-black text-sm"
+ >
+ Go back
+
+
+ Start Over
+
+
+
+ )
+}
diff --git a/examples/react/start-basic-static/app/routeTree.gen.ts b/examples/react/start-basic-static/app/routeTree.gen.ts
new file mode 100644
index 0000000000..9f7ab8107b
--- /dev/null
+++ b/examples/react/start-basic-static/app/routeTree.gen.ts
@@ -0,0 +1,470 @@
+/* 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 Routes
+
+import { Route as rootRoute } from './routes/__root'
+import { Route as UsersImport } from './routes/users'
+import { Route as RedirectImport } from './routes/redirect'
+import { Route as PostsImport } from './routes/posts'
+import { Route as DeferredImport } from './routes/deferred'
+import { Route as LayoutImport } from './routes/_layout'
+import { Route as IndexImport } from './routes/index'
+import { Route as UsersIndexImport } from './routes/users.index'
+import { Route as PostsIndexImport } from './routes/posts.index'
+import { Route as UsersUserIdImport } from './routes/users.$userId'
+import { Route as PostsPostIdImport } from './routes/posts.$postId'
+import { Route as LayoutLayout2Import } from './routes/_layout/_layout-2'
+import { Route as PostsPostIdDeepImport } from './routes/posts_.$postId.deep'
+import { Route as LayoutLayout2LayoutBImport } from './routes/_layout/_layout-2/layout-b'
+import { Route as LayoutLayout2LayoutAImport } from './routes/_layout/_layout-2/layout-a'
+
+// Create/Update Routes
+
+const UsersRoute = UsersImport.update({
+ id: '/users',
+ path: '/users',
+ getParentRoute: () => rootRoute,
+} as any)
+
+const RedirectRoute = RedirectImport.update({
+ id: '/redirect',
+ path: '/redirect',
+ getParentRoute: () => rootRoute,
+} as any)
+
+const PostsRoute = PostsImport.update({
+ id: '/posts',
+ path: '/posts',
+ getParentRoute: () => rootRoute,
+} as any)
+
+const DeferredRoute = DeferredImport.update({
+ id: '/deferred',
+ path: '/deferred',
+ getParentRoute: () => rootRoute,
+} as any)
+
+const LayoutRoute = LayoutImport.update({
+ id: '/_layout',
+ getParentRoute: () => rootRoute,
+} as any)
+
+const IndexRoute = IndexImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => rootRoute,
+} as any)
+
+const UsersIndexRoute = UsersIndexImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => UsersRoute,
+} as any)
+
+const PostsIndexRoute = PostsIndexImport.update({
+ id: '/',
+ path: '/',
+ getParentRoute: () => PostsRoute,
+} as any)
+
+const UsersUserIdRoute = UsersUserIdImport.update({
+ id: '/$userId',
+ path: '/$userId',
+ getParentRoute: () => UsersRoute,
+} as any)
+
+const PostsPostIdRoute = PostsPostIdImport.update({
+ id: '/$postId',
+ path: '/$postId',
+ getParentRoute: () => PostsRoute,
+} as any)
+
+const LayoutLayout2Route = LayoutLayout2Import.update({
+ id: '/_layout-2',
+ getParentRoute: () => LayoutRoute,
+} as any)
+
+const PostsPostIdDeepRoute = PostsPostIdDeepImport.update({
+ id: '/posts_/$postId/deep',
+ path: '/posts/$postId/deep',
+ getParentRoute: () => rootRoute,
+} as any)
+
+const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBImport.update({
+ id: '/layout-b',
+ path: '/layout-b',
+ getParentRoute: () => LayoutLayout2Route,
+} as any)
+
+const LayoutLayout2LayoutARoute = LayoutLayout2LayoutAImport.update({
+ id: '/layout-a',
+ path: '/layout-a',
+ getParentRoute: () => LayoutLayout2Route,
+} as any)
+
+// Populate the FileRoutesByPath interface
+
+declare module '@tanstack/react-router' {
+ interface FileRoutesByPath {
+ '/': {
+ id: '/'
+ path: '/'
+ fullPath: '/'
+ preLoaderRoute: typeof IndexImport
+ parentRoute: typeof rootRoute
+ }
+ '/_layout': {
+ id: '/_layout'
+ path: ''
+ fullPath: ''
+ preLoaderRoute: typeof LayoutImport
+ parentRoute: typeof rootRoute
+ }
+ '/deferred': {
+ id: '/deferred'
+ path: '/deferred'
+ fullPath: '/deferred'
+ preLoaderRoute: typeof DeferredImport
+ parentRoute: typeof rootRoute
+ }
+ '/posts': {
+ id: '/posts'
+ path: '/posts'
+ fullPath: '/posts'
+ preLoaderRoute: typeof PostsImport
+ parentRoute: typeof rootRoute
+ }
+ '/redirect': {
+ id: '/redirect'
+ path: '/redirect'
+ fullPath: '/redirect'
+ preLoaderRoute: typeof RedirectImport
+ parentRoute: typeof rootRoute
+ }
+ '/users': {
+ id: '/users'
+ path: '/users'
+ fullPath: '/users'
+ preLoaderRoute: typeof UsersImport
+ parentRoute: typeof rootRoute
+ }
+ '/_layout/_layout-2': {
+ id: '/_layout/_layout-2'
+ path: ''
+ fullPath: ''
+ preLoaderRoute: typeof LayoutLayout2Import
+ parentRoute: typeof LayoutImport
+ }
+ '/posts/$postId': {
+ id: '/posts/$postId'
+ path: '/$postId'
+ fullPath: '/posts/$postId'
+ preLoaderRoute: typeof PostsPostIdImport
+ parentRoute: typeof PostsImport
+ }
+ '/users/$userId': {
+ id: '/users/$userId'
+ path: '/$userId'
+ fullPath: '/users/$userId'
+ preLoaderRoute: typeof UsersUserIdImport
+ parentRoute: typeof UsersImport
+ }
+ '/posts/': {
+ id: '/posts/'
+ path: '/'
+ fullPath: '/posts/'
+ preLoaderRoute: typeof PostsIndexImport
+ parentRoute: typeof PostsImport
+ }
+ '/users/': {
+ id: '/users/'
+ path: '/'
+ fullPath: '/users/'
+ preLoaderRoute: typeof UsersIndexImport
+ parentRoute: typeof UsersImport
+ }
+ '/_layout/_layout-2/layout-a': {
+ id: '/_layout/_layout-2/layout-a'
+ path: '/layout-a'
+ fullPath: '/layout-a'
+ preLoaderRoute: typeof LayoutLayout2LayoutAImport
+ parentRoute: typeof LayoutLayout2Import
+ }
+ '/_layout/_layout-2/layout-b': {
+ id: '/_layout/_layout-2/layout-b'
+ path: '/layout-b'
+ fullPath: '/layout-b'
+ preLoaderRoute: typeof LayoutLayout2LayoutBImport
+ parentRoute: typeof LayoutLayout2Import
+ }
+ '/posts_/$postId/deep': {
+ id: '/posts_/$postId/deep'
+ path: '/posts/$postId/deep'
+ fullPath: '/posts/$postId/deep'
+ preLoaderRoute: typeof PostsPostIdDeepImport
+ parentRoute: typeof rootRoute
+ }
+ }
+}
+
+// Create and export the route tree
+
+interface LayoutLayout2RouteChildren {
+ LayoutLayout2LayoutARoute: typeof LayoutLayout2LayoutARoute
+ LayoutLayout2LayoutBRoute: typeof LayoutLayout2LayoutBRoute
+}
+
+const LayoutLayout2RouteChildren: LayoutLayout2RouteChildren = {
+ LayoutLayout2LayoutARoute: LayoutLayout2LayoutARoute,
+ LayoutLayout2LayoutBRoute: LayoutLayout2LayoutBRoute,
+}
+
+const LayoutLayout2RouteWithChildren = LayoutLayout2Route._addFileChildren(
+ LayoutLayout2RouteChildren,
+)
+
+interface LayoutRouteChildren {
+ LayoutLayout2Route: typeof LayoutLayout2RouteWithChildren
+}
+
+const LayoutRouteChildren: LayoutRouteChildren = {
+ LayoutLayout2Route: LayoutLayout2RouteWithChildren,
+}
+
+const LayoutRouteWithChildren =
+ LayoutRoute._addFileChildren(LayoutRouteChildren)
+
+interface PostsRouteChildren {
+ PostsPostIdRoute: typeof PostsPostIdRoute
+ PostsIndexRoute: typeof PostsIndexRoute
+}
+
+const PostsRouteChildren: PostsRouteChildren = {
+ PostsPostIdRoute: PostsPostIdRoute,
+ PostsIndexRoute: PostsIndexRoute,
+}
+
+const PostsRouteWithChildren = PostsRoute._addFileChildren(PostsRouteChildren)
+
+interface UsersRouteChildren {
+ UsersUserIdRoute: typeof UsersUserIdRoute
+ UsersIndexRoute: typeof UsersIndexRoute
+}
+
+const UsersRouteChildren: UsersRouteChildren = {
+ UsersUserIdRoute: UsersUserIdRoute,
+ UsersIndexRoute: UsersIndexRoute,
+}
+
+const UsersRouteWithChildren = UsersRoute._addFileChildren(UsersRouteChildren)
+
+export interface FileRoutesByFullPath {
+ '/': typeof IndexRoute
+ '': typeof LayoutLayout2RouteWithChildren
+ '/deferred': typeof DeferredRoute
+ '/posts': typeof PostsRouteWithChildren
+ '/redirect': typeof RedirectRoute
+ '/users': typeof UsersRouteWithChildren
+ '/posts/$postId': typeof PostsPostIdRoute
+ '/users/$userId': typeof UsersUserIdRoute
+ '/posts/': typeof PostsIndexRoute
+ '/users/': typeof UsersIndexRoute
+ '/layout-a': typeof LayoutLayout2LayoutARoute
+ '/layout-b': typeof LayoutLayout2LayoutBRoute
+ '/posts/$postId/deep': typeof PostsPostIdDeepRoute
+}
+
+export interface FileRoutesByTo {
+ '/': typeof IndexRoute
+ '': typeof LayoutLayout2RouteWithChildren
+ '/deferred': typeof DeferredRoute
+ '/redirect': typeof RedirectRoute
+ '/posts/$postId': typeof PostsPostIdRoute
+ '/users/$userId': typeof UsersUserIdRoute
+ '/posts': typeof PostsIndexRoute
+ '/users': typeof UsersIndexRoute
+ '/layout-a': typeof LayoutLayout2LayoutARoute
+ '/layout-b': typeof LayoutLayout2LayoutBRoute
+ '/posts/$postId/deep': typeof PostsPostIdDeepRoute
+}
+
+export interface FileRoutesById {
+ __root__: typeof rootRoute
+ '/': typeof IndexRoute
+ '/_layout': typeof LayoutRouteWithChildren
+ '/deferred': typeof DeferredRoute
+ '/posts': typeof PostsRouteWithChildren
+ '/redirect': typeof RedirectRoute
+ '/users': typeof UsersRouteWithChildren
+ '/_layout/_layout-2': typeof LayoutLayout2RouteWithChildren
+ '/posts/$postId': typeof PostsPostIdRoute
+ '/users/$userId': typeof UsersUserIdRoute
+ '/posts/': typeof PostsIndexRoute
+ '/users/': typeof UsersIndexRoute
+ '/_layout/_layout-2/layout-a': typeof LayoutLayout2LayoutARoute
+ '/_layout/_layout-2/layout-b': typeof LayoutLayout2LayoutBRoute
+ '/posts_/$postId/deep': typeof PostsPostIdDeepRoute
+}
+
+export interface FileRouteTypes {
+ fileRoutesByFullPath: FileRoutesByFullPath
+ fullPaths:
+ | '/'
+ | ''
+ | '/deferred'
+ | '/posts'
+ | '/redirect'
+ | '/users'
+ | '/posts/$postId'
+ | '/users/$userId'
+ | '/posts/'
+ | '/users/'
+ | '/layout-a'
+ | '/layout-b'
+ | '/posts/$postId/deep'
+ fileRoutesByTo: FileRoutesByTo
+ to:
+ | '/'
+ | ''
+ | '/deferred'
+ | '/redirect'
+ | '/posts/$postId'
+ | '/users/$userId'
+ | '/posts'
+ | '/users'
+ | '/layout-a'
+ | '/layout-b'
+ | '/posts/$postId/deep'
+ id:
+ | '__root__'
+ | '/'
+ | '/_layout'
+ | '/deferred'
+ | '/posts'
+ | '/redirect'
+ | '/users'
+ | '/_layout/_layout-2'
+ | '/posts/$postId'
+ | '/users/$userId'
+ | '/posts/'
+ | '/users/'
+ | '/_layout/_layout-2/layout-a'
+ | '/_layout/_layout-2/layout-b'
+ | '/posts_/$postId/deep'
+ fileRoutesById: FileRoutesById
+}
+
+export interface RootRouteChildren {
+ IndexRoute: typeof IndexRoute
+ LayoutRoute: typeof LayoutRouteWithChildren
+ DeferredRoute: typeof DeferredRoute
+ PostsRoute: typeof PostsRouteWithChildren
+ RedirectRoute: typeof RedirectRoute
+ UsersRoute: typeof UsersRouteWithChildren
+ PostsPostIdDeepRoute: typeof PostsPostIdDeepRoute
+}
+
+const rootRouteChildren: RootRouteChildren = {
+ IndexRoute: IndexRoute,
+ LayoutRoute: LayoutRouteWithChildren,
+ DeferredRoute: DeferredRoute,
+ PostsRoute: PostsRouteWithChildren,
+ RedirectRoute: RedirectRoute,
+ UsersRoute: UsersRouteWithChildren,
+ PostsPostIdDeepRoute: PostsPostIdDeepRoute,
+}
+
+export const routeTree = rootRoute
+ ._addFileChildren(rootRouteChildren)
+ ._addFileTypes()
+
+/* ROUTE_MANIFEST_START
+{
+ "routes": {
+ "__root__": {
+ "filePath": "__root.tsx",
+ "children": [
+ "/",
+ "/_layout",
+ "/deferred",
+ "/posts",
+ "/redirect",
+ "/users",
+ "/posts_/$postId/deep"
+ ]
+ },
+ "/": {
+ "filePath": "index.tsx"
+ },
+ "/_layout": {
+ "filePath": "_layout.tsx",
+ "children": [
+ "/_layout/_layout-2"
+ ]
+ },
+ "/deferred": {
+ "filePath": "deferred.tsx"
+ },
+ "/posts": {
+ "filePath": "posts.tsx",
+ "children": [
+ "/posts/$postId",
+ "/posts/"
+ ]
+ },
+ "/redirect": {
+ "filePath": "redirect.tsx"
+ },
+ "/users": {
+ "filePath": "users.tsx",
+ "children": [
+ "/users/$userId",
+ "/users/"
+ ]
+ },
+ "/_layout/_layout-2": {
+ "filePath": "_layout/_layout-2.tsx",
+ "parent": "/_layout",
+ "children": [
+ "/_layout/_layout-2/layout-a",
+ "/_layout/_layout-2/layout-b"
+ ]
+ },
+ "/posts/$postId": {
+ "filePath": "posts.$postId.tsx",
+ "parent": "/posts"
+ },
+ "/users/$userId": {
+ "filePath": "users.$userId.tsx",
+ "parent": "/users"
+ },
+ "/posts/": {
+ "filePath": "posts.index.tsx",
+ "parent": "/posts"
+ },
+ "/users/": {
+ "filePath": "users.index.tsx",
+ "parent": "/users"
+ },
+ "/_layout/_layout-2/layout-a": {
+ "filePath": "_layout/_layout-2/layout-a.tsx",
+ "parent": "/_layout/_layout-2"
+ },
+ "/_layout/_layout-2/layout-b": {
+ "filePath": "_layout/_layout-2/layout-b.tsx",
+ "parent": "/_layout/_layout-2"
+ },
+ "/posts_/$postId/deep": {
+ "filePath": "posts_.$postId.deep.tsx"
+ }
+ }
+}
+ROUTE_MANIFEST_END */
diff --git a/examples/react/start-basic-static/app/router.tsx b/examples/react/start-basic-static/app/router.tsx
new file mode 100644
index 0000000000..0886de701f
--- /dev/null
+++ b/examples/react/start-basic-static/app/router.tsx
@@ -0,0 +1,21 @@
+import { createRouter as createTanStackRouter } from '@tanstack/react-router'
+import { routeTree } from './routeTree.gen'
+import { DefaultCatchBoundary } from './components/DefaultCatchBoundary'
+import { NotFound } from './components/NotFound'
+
+export function createRouter() {
+ const router = createTanStackRouter({
+ routeTree,
+ defaultPreload: 'intent',
+ defaultErrorComponent: DefaultCatchBoundary,
+ defaultNotFoundComponent: () => ,
+ })
+
+ return router
+}
+
+declare module '@tanstack/react-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/examples/react/start-basic-static/app/routes/__root.tsx b/examples/react/start-basic-static/app/routes/__root.tsx
new file mode 100644
index 0000000000..a2376f8364
--- /dev/null
+++ b/examples/react/start-basic-static/app/routes/__root.tsx
@@ -0,0 +1,140 @@
+import {
+ Link,
+ Outlet,
+ ScrollRestoration,
+ createRootRoute,
+} from '@tanstack/react-router'
+import { TanStackRouterDevtools } from '@tanstack/router-devtools'
+import { Meta, Scripts } from '@tanstack/start'
+import * as React from 'react'
+import { DefaultCatchBoundary } from '~/components/DefaultCatchBoundary'
+import { NotFound } from '~/components/NotFound'
+import appCss from '~/styles/app.css?url'
+import { seo } from '~/utils/seo'
+
+export const Route = createRootRoute({
+ head: () => ({
+ meta: [
+ {
+ charSet: 'utf-8',
+ },
+ {
+ name: 'viewport',
+ content: 'width=device-width, initial-scale=1',
+ },
+ ...seo({
+ title:
+ 'TanStack Start | Type-Safe, Client-First, Full-Stack React Framework',
+ description: `TanStack Start is a type-safe, client-first, full-stack React framework. `,
+ }),
+ ],
+ links: [
+ { rel: 'stylesheet', href: appCss },
+ {
+ rel: 'apple-touch-icon',
+ sizes: '180x180',
+ href: '/apple-touch-icon.png',
+ },
+ {
+ rel: 'icon',
+ type: 'image/png',
+ sizes: '32x32',
+ href: '/favicon-32x32.png',
+ },
+ {
+ rel: 'icon',
+ type: 'image/png',
+ sizes: '16x16',
+ href: '/favicon-16x16.png',
+ },
+ { rel: 'manifest', href: '/site.webmanifest', color: '#fffff' },
+ { rel: 'icon', href: '/favicon.ico' },
+ ],
+ }),
+ errorComponent: (props) => {
+ return (
+
+
+
+ )
+ },
+ notFoundComponent: () => ,
+ component: RootComponent,
+})
+
+function RootComponent() {
+ return (
+
+
+
+ )
+}
+
+function RootDocument({ children }: { children: React.ReactNode }) {
+ return (
+
+
+
+
+
+
+
+ Home
+ {' '}
+
+ Posts
+ {' '}
+
+ Users
+ {' '}
+
+ Layout
+ {' '}
+
+ Deferred
+ {' '}
+
+ This Route Does Not Exist
+
+
+
+ {children}
+
+
+
+
+
+ )
+}
diff --git a/examples/react/start-basic-static/app/routes/_layout.tsx b/examples/react/start-basic-static/app/routes/_layout.tsx
new file mode 100644
index 0000000000..02ddbb1cd9
--- /dev/null
+++ b/examples/react/start-basic-static/app/routes/_layout.tsx
@@ -0,0 +1,16 @@
+import { Outlet, createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/_layout')({
+ component: LayoutComponent,
+})
+
+function LayoutComponent() {
+ return (
+
+ )
+}
diff --git a/examples/react/start-basic-static/app/routes/_layout/_layout-2.tsx b/examples/react/start-basic-static/app/routes/_layout/_layout-2.tsx
new file mode 100644
index 0000000000..3b7dbf2903
--- /dev/null
+++ b/examples/react/start-basic-static/app/routes/_layout/_layout-2.tsx
@@ -0,0 +1,34 @@
+import { Link, Outlet, createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/_layout/_layout-2')({
+ component: LayoutComponent,
+})
+
+function LayoutComponent() {
+ return (
+
+
I'm a nested layout
+
+
+ Layout A
+
+
+ Layout B
+
+
+
+
+
+
+ )
+}
diff --git a/examples/react/start-basic-static/app/routes/_layout/_layout-2/layout-a.tsx b/examples/react/start-basic-static/app/routes/_layout/_layout-2/layout-a.tsx
new file mode 100644
index 0000000000..61e19b4d9f
--- /dev/null
+++ b/examples/react/start-basic-static/app/routes/_layout/_layout-2/layout-a.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/_layout/_layout-2/layout-a')({
+ component: LayoutAComponent,
+})
+
+function LayoutAComponent() {
+ return I'm layout A!
+}
diff --git a/examples/react/start-basic-static/app/routes/_layout/_layout-2/layout-b.tsx b/examples/react/start-basic-static/app/routes/_layout/_layout-2/layout-b.tsx
new file mode 100644
index 0000000000..cceed1fb9a
--- /dev/null
+++ b/examples/react/start-basic-static/app/routes/_layout/_layout-2/layout-b.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/_layout/_layout-2/layout-b')({
+ component: LayoutBComponent,
+})
+
+function LayoutBComponent() {
+ return I'm layout B!
+}
diff --git a/examples/react/start-basic-static/app/routes/deferred.tsx b/examples/react/start-basic-static/app/routes/deferred.tsx
new file mode 100644
index 0000000000..dd5511f692
--- /dev/null
+++ b/examples/react/start-basic-static/app/routes/deferred.tsx
@@ -0,0 +1,62 @@
+import { Await, createFileRoute } from '@tanstack/react-router'
+import { createServerFn } from '@tanstack/start'
+import { Suspense, useState } from 'react'
+
+const personServerFn = createServerFn({ method: 'GET', type: 'static' })
+ .validator((d: string) => d)
+ .handler(({ data: name }) => {
+ return { name, randomNumber: Math.floor(Math.random() * 100) }
+ })
+
+const slowServerFn = createServerFn({ method: 'GET', type: 'static' })
+ .validator((d: string) => d)
+ .handler(async ({ data: name }) => {
+ await new Promise((r) => setTimeout(r, 1000))
+ return { name, randomNumber: Math.floor(Math.random() * 100) }
+ })
+
+export const Route = createFileRoute('/deferred')({
+ loader: async () => {
+ return {
+ deferredStuff: new Promise((r) =>
+ setTimeout(() => r('Hello deferred!'), 2000),
+ ),
+ deferredPerson: slowServerFn({ data: 'Tanner Linsley' }),
+ person: await personServerFn({ data: 'John Doe' }),
+ }
+ },
+ component: Deferred,
+})
+
+function Deferred() {
+ const [count, setCount] = useState(0)
+ const { deferredStuff, deferredPerson, person } = Route.useLoaderData()
+
+ return (
+
+
+ {person.name} - {person.randomNumber}
+
+
Loading person... }>
+ (
+
+ {data.name} - {data.randomNumber}
+
+ )}
+ />
+
+ Loading stuff...}>
+ {data} }
+ />
+
+ Count: {count}
+
+ setCount(count + 1)}>Increment
+
+
+ )
+}
diff --git a/examples/react/start-basic-static/app/routes/index.tsx b/examples/react/start-basic-static/app/routes/index.tsx
new file mode 100644
index 0000000000..09a907cb18
--- /dev/null
+++ b/examples/react/start-basic-static/app/routes/index.tsx
@@ -0,0 +1,13 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/')({
+ component: Home,
+})
+
+function Home() {
+ return (
+
+
Welcome Home!!!
+
+ )
+}
diff --git a/examples/react/start-basic-static/app/routes/posts.$postId.tsx b/examples/react/start-basic-static/app/routes/posts.$postId.tsx
new file mode 100644
index 0000000000..0d4d2de8eb
--- /dev/null
+++ b/examples/react/start-basic-static/app/routes/posts.$postId.tsx
@@ -0,0 +1,38 @@
+import { ErrorComponent, Link, createFileRoute } from '@tanstack/react-router'
+import { fetchPost } from '../utils/posts'
+import type { ErrorComponentProps } from '@tanstack/react-router'
+import { NotFound } from '~/components/NotFound'
+
+export const Route = createFileRoute('/posts/$postId')({
+ loader: ({ params: { postId } }) => fetchPost({ data: postId }),
+ errorComponent: PostErrorComponent,
+ component: PostComponent,
+ notFoundComponent: () => {
+ return Post not found
+ },
+})
+
+export function PostErrorComponent({ error }: ErrorComponentProps) {
+ return
+}
+
+function PostComponent() {
+ const post = Route.useLoaderData()
+
+ return (
+
+
{post.title}
+
{post.body}
+
+ Deep View
+
+
+ )
+}
diff --git a/examples/react/start-basic-static/app/routes/posts.index.tsx b/examples/react/start-basic-static/app/routes/posts.index.tsx
new file mode 100644
index 0000000000..5b5f08f95b
--- /dev/null
+++ b/examples/react/start-basic-static/app/routes/posts.index.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/posts/')({
+ component: PostsIndexComponent,
+})
+
+function PostsIndexComponent() {
+ return Select a post.
+}
diff --git a/examples/react/start-basic-static/app/routes/posts.tsx b/examples/react/start-basic-static/app/routes/posts.tsx
new file mode 100644
index 0000000000..ae49032459
--- /dev/null
+++ b/examples/react/start-basic-static/app/routes/posts.tsx
@@ -0,0 +1,38 @@
+import { Link, Outlet, createFileRoute } from '@tanstack/react-router'
+import { fetchPosts } from '../utils/posts'
+
+export const Route = createFileRoute('/posts')({
+ loader: async () => fetchPosts(),
+ component: PostsComponent,
+})
+
+function PostsComponent() {
+ const posts = Route.useLoaderData()
+
+ return (
+
+ )
+}
diff --git a/examples/react/start-basic-static/app/routes/posts_.$postId.deep.tsx b/examples/react/start-basic-static/app/routes/posts_.$postId.deep.tsx
new file mode 100644
index 0000000000..a82d2e3211
--- /dev/null
+++ b/examples/react/start-basic-static/app/routes/posts_.$postId.deep.tsx
@@ -0,0 +1,29 @@
+import { Link, createFileRoute } from '@tanstack/react-router'
+import { fetchPost } from '../utils/posts'
+import { PostErrorComponent } from './posts.$postId'
+
+export const Route = createFileRoute('/posts_/$postId/deep')({
+ loader: ({ params: { postId } }) =>
+ fetchPost({
+ data: postId,
+ }),
+ errorComponent: PostErrorComponent,
+ component: PostDeepComponent,
+})
+
+function PostDeepComponent() {
+ const post = Route.useLoaderData()
+
+ return (
+
+
+ ← All Posts
+
+
{post.title}
+
{post.body}
+
+ )
+}
diff --git a/examples/react/start-basic-static/app/routes/redirect.tsx b/examples/react/start-basic-static/app/routes/redirect.tsx
new file mode 100644
index 0000000000..c9286de13d
--- /dev/null
+++ b/examples/react/start-basic-static/app/routes/redirect.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute, redirect } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/redirect')({
+ beforeLoad: async () => {
+ throw redirect({
+ to: '/posts',
+ })
+ },
+})
diff --git a/examples/react/start-basic-static/app/routes/users.$userId.tsx b/examples/react/start-basic-static/app/routes/users.$userId.tsx
new file mode 100644
index 0000000000..1a797d0978
--- /dev/null
+++ b/examples/react/start-basic-static/app/routes/users.$userId.tsx
@@ -0,0 +1,55 @@
+import { ErrorComponent, createFileRoute } from '@tanstack/react-router'
+import axios from 'redaxios'
+import { setResponseStatus } from 'vinxi/http'
+import { createServerFn } from '@tanstack/start'
+import type { ErrorComponentProps } from '@tanstack/react-router'
+import type { User } from '~/utils/users'
+import { NotFound } from '~/components/NotFound'
+
+const fetchUser = createServerFn({ method: 'GET' })
+ .validator((d: string) => d)
+ .type(({ data }) => (+data % 2 === 0 ? 'static' : 'dynamic'))
+ .handler(async ({ data: userId }) => {
+ try {
+ return axios
+ .get('https://jsonplaceholder.typicode.com/users/' + userId)
+ .then((d) => ({
+ id: d.data.id,
+ name: d.data.name,
+ email: d.data.email,
+ }))
+ } catch (e) {
+ console.error(e)
+ setResponseStatus(404)
+
+ return { error: 'User not found' }
+ }
+ })
+
+export const Route = createFileRoute('/users/$userId')({
+ loader: ({ params: { userId } }) => fetchUser({ data: userId }),
+ errorComponent: UserErrorComponent,
+ component: UserComponent,
+ notFoundComponent: () => {
+ return User not found
+ },
+})
+
+export function UserErrorComponent({ error }: ErrorComponentProps) {
+ return
+}
+
+function UserComponent() {
+ const user = Route.useLoaderData()
+
+ if ('error' in user) {
+ return User not found
+ }
+
+ return (
+
+
{user.name}
+
{user.email}
+
+ )
+}
diff --git a/examples/react/start-basic-static/app/routes/users.index.tsx b/examples/react/start-basic-static/app/routes/users.index.tsx
new file mode 100644
index 0000000000..b6b0ee67fb
--- /dev/null
+++ b/examples/react/start-basic-static/app/routes/users.index.tsx
@@ -0,0 +1,9 @@
+import { createFileRoute } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/users/')({
+ component: UsersIndexComponent,
+})
+
+function UsersIndexComponent() {
+ return Select a user.
+}
diff --git a/examples/react/start-basic-static/app/routes/users.tsx b/examples/react/start-basic-static/app/routes/users.tsx
new file mode 100644
index 0000000000..2ebf718a75
--- /dev/null
+++ b/examples/react/start-basic-static/app/routes/users.tsx
@@ -0,0 +1,54 @@
+import { Link, Outlet, createFileRoute } from '@tanstack/react-router'
+import axios from 'redaxios'
+import { createServerFn } from '@tanstack/start'
+import type { User } from '../utils/users'
+
+const fetchUsers = createServerFn({ method: 'GET', type: 'static' }).handler(
+ async () => {
+ console.info('Fetching users...')
+ const res = await axios.get>(
+ 'https://jsonplaceholder.typicode.com/users',
+ )
+
+ return res.data
+ .slice(0, 10)
+ .map((u) => ({ id: u.id, name: u.name, email: u.email }))
+ },
+)
+
+export const Route = createFileRoute('/users')({
+ loader: async () => fetchUsers(),
+ component: UsersComponent,
+})
+
+function UsersComponent() {
+ const users = Route.useLoaderData()
+
+ return (
+
+
+ {[
+ ...users,
+ { id: 'i-do-not-exist', name: 'Non-existent User', email: '' },
+ ].map((user) => {
+ return (
+
+
+ {user.name}
+
+
+ )
+ })}
+
+
+
+
+ )
+}
diff --git a/examples/react/start-basic-static/app/ssr.tsx b/examples/react/start-basic-static/app/ssr.tsx
new file mode 100644
index 0000000000..f2d33f9030
--- /dev/null
+++ b/examples/react/start-basic-static/app/ssr.tsx
@@ -0,0 +1,13 @@
+///
+import {
+ createStartHandler,
+ defaultStreamHandler,
+} from '@tanstack/start/server'
+import { getRouterManifest } from '@tanstack/start/router-manifest'
+
+import { createRouter } from './router'
+
+export default createStartHandler({
+ createRouter,
+ getRouterManifest,
+})(defaultStreamHandler)
diff --git a/examples/react/start-basic-static/app/styles/app.css b/examples/react/start-basic-static/app/styles/app.css
new file mode 100644
index 0000000000..d6426ccb72
--- /dev/null
+++ b/examples/react/start-basic-static/app/styles/app.css
@@ -0,0 +1,14 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@layer base {
+ html,
+ body {
+ @apply text-gray-900 bg-gray-50 dark:bg-gray-950 dark:text-gray-200;
+ }
+
+ .using-mouse * {
+ outline: none !important;
+ }
+}
diff --git a/examples/react/start-basic-static/app/utils/loggingMiddleware.tsx b/examples/react/start-basic-static/app/utils/loggingMiddleware.tsx
new file mode 100644
index 0000000000..52c90185d0
--- /dev/null
+++ b/examples/react/start-basic-static/app/utils/loggingMiddleware.tsx
@@ -0,0 +1,37 @@
+import { createMiddleware } from '@tanstack/start'
+
+export const logMiddleware = createMiddleware()
+ .client(async (ctx) => {
+ const clientTime = new Date()
+
+ return ctx.next({
+ context: {
+ clientTime,
+ },
+ sendContext: {
+ clientTime,
+ },
+ })
+ })
+ .server(async (ctx) => {
+ const serverTime = new Date()
+
+ return ctx.next({
+ sendContext: {
+ serverTime,
+ durationToServer:
+ serverTime.getTime() - ctx.context.clientTime.getTime(),
+ },
+ })
+ })
+ .clientAfter(async (ctx) => {
+ const now = new Date()
+
+ console.log('Client Req/Res:', {
+ duration: ctx.context.clientTime.getTime() - now.getTime(),
+ durationToServer: ctx.context.durationToServer,
+ durationFromServer: now.getTime() - ctx.context.serverTime.getTime(),
+ })
+
+ return ctx.next()
+ })
diff --git a/examples/react/start-basic-static/app/utils/posts.tsx b/examples/react/start-basic-static/app/utils/posts.tsx
new file mode 100644
index 0000000000..050ec0d86c
--- /dev/null
+++ b/examples/react/start-basic-static/app/utils/posts.tsx
@@ -0,0 +1,37 @@
+import { notFound } from '@tanstack/react-router'
+import { createServerFn } from '@tanstack/start'
+import axios from 'redaxios'
+import { logMiddleware } from './loggingMiddleware'
+
+export type PostType = {
+ id: string
+ title: string
+ body: string
+}
+
+export const fetchPost = createServerFn({ method: 'GET', type: 'static' })
+ .middleware([logMiddleware])
+ .validator((d: string) => d)
+ .handler(async ({ data }) => {
+ console.info(`Fetching post with id ${data}...`)
+ const post = await axios
+ .get(`https://jsonplaceholder.typicode.com/posts/${data}`)
+ .then((r) => r.data)
+ .catch((err) => {
+ if (err.status === 404) {
+ throw notFound()
+ }
+ throw err
+ })
+
+ return post
+ })
+
+export const fetchPosts = createServerFn({ method: 'GET', type: 'static' })
+ .middleware([logMiddleware])
+ .handler(async () => {
+ console.info('Fetching posts...')
+ return axios
+ .get>('https://jsonplaceholder.typicode.com/posts')
+ .then((r) => r.data.slice(0, 10))
+ })
diff --git a/examples/react/start-basic-static/app/utils/seo.ts b/examples/react/start-basic-static/app/utils/seo.ts
new file mode 100644
index 0000000000..d18ad84b74
--- /dev/null
+++ b/examples/react/start-basic-static/app/utils/seo.ts
@@ -0,0 +1,33 @@
+export const seo = ({
+ title,
+ description,
+ keywords,
+ image,
+}: {
+ title: string
+ description?: string
+ image?: string
+ keywords?: string
+}) => {
+ const tags = [
+ { title },
+ { name: 'description', content: description },
+ { name: 'keywords', content: keywords },
+ { name: 'twitter:title', content: title },
+ { name: 'twitter:description', content: description },
+ { name: 'twitter:creator', content: '@tannerlinsley' },
+ { name: 'twitter:site', content: '@tannerlinsley' },
+ { name: 'og:type', content: 'website' },
+ { name: 'og:title', content: title },
+ { name: 'og:description', content: description },
+ ...(image
+ ? [
+ { name: 'twitter:image', content: image },
+ { name: 'twitter:card', content: 'summary_large_image' },
+ { name: 'og:image', content: image },
+ ]
+ : []),
+ ]
+
+ return tags
+}
diff --git a/examples/react/start-basic-static/app/utils/users.tsx b/examples/react/start-basic-static/app/utils/users.tsx
new file mode 100644
index 0000000000..b810f455fe
--- /dev/null
+++ b/examples/react/start-basic-static/app/utils/users.tsx
@@ -0,0 +1,7 @@
+export type User = {
+ id: number
+ name: string
+ email: string
+}
+
+export const DEPLOY_URL = 'http://localhost:3000'
diff --git a/examples/react/start-basic-static/package.json b/examples/react/start-basic-static/package.json
new file mode 100644
index 0000000000..78aa67a6e4
--- /dev/null
+++ b/examples/react/start-basic-static/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "tanstack-start-example-basic-static",
+ "private": true,
+ "sideEffects": false,
+ "type": "module",
+ "scripts": {
+ "dev": "vinxi dev",
+ "build": "vinxi build",
+ "start": "vinxi start"
+ },
+ "dependencies": {
+ "@tanstack/react-router": "^1.84.4",
+ "@tanstack/router-devtools": "^1.84.4",
+ "@tanstack/start": "^1.84.4",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "redaxios": "^0.5.1",
+ "tailwind-merge": "^2.5.5",
+ "vinxi": "0.4.3"
+ },
+ "devDependencies": {
+ "@types/node": "^22.5.4",
+ "@types/react": "^18.2.65",
+ "@types/react-dom": "^18.2.21",
+ "autoprefixer": "^10.4.20",
+ "postcss": "^8.4.49",
+ "tailwindcss": "^3.4.15",
+ "typescript": "^5.6.2",
+ "vite-tsconfig-paths": "^5.1.3"
+ }
+}
diff --git a/examples/react/start-basic-static/postcss.config.cjs b/examples/react/start-basic-static/postcss.config.cjs
new file mode 100644
index 0000000000..8e638a6bcd
--- /dev/null
+++ b/examples/react/start-basic-static/postcss.config.cjs
@@ -0,0 +1,7 @@
+module.exports = {
+ plugins: [
+ require('tailwindcss/nesting'),
+ require('tailwindcss'),
+ require('autoprefixer'),
+ ],
+}
diff --git a/examples/react/start-basic-static/public/android-chrome-192x192.png b/examples/react/start-basic-static/public/android-chrome-192x192.png
new file mode 100644
index 0000000000..09c8324f8c
Binary files /dev/null and b/examples/react/start-basic-static/public/android-chrome-192x192.png differ
diff --git a/examples/react/start-basic-static/public/android-chrome-512x512.png b/examples/react/start-basic-static/public/android-chrome-512x512.png
new file mode 100644
index 0000000000..11d626ea3d
Binary files /dev/null and b/examples/react/start-basic-static/public/android-chrome-512x512.png differ
diff --git a/examples/react/start-basic-static/public/apple-touch-icon.png b/examples/react/start-basic-static/public/apple-touch-icon.png
new file mode 100644
index 0000000000..5a9423cc02
Binary files /dev/null and b/examples/react/start-basic-static/public/apple-touch-icon.png differ
diff --git a/examples/react/start-basic-static/public/favicon-16x16.png b/examples/react/start-basic-static/public/favicon-16x16.png
new file mode 100644
index 0000000000..e3389b0044
Binary files /dev/null and b/examples/react/start-basic-static/public/favicon-16x16.png differ
diff --git a/examples/react/start-basic-static/public/favicon-32x32.png b/examples/react/start-basic-static/public/favicon-32x32.png
new file mode 100644
index 0000000000..900c77d444
Binary files /dev/null and b/examples/react/start-basic-static/public/favicon-32x32.png differ
diff --git a/examples/react/start-basic-static/public/favicon.ico b/examples/react/start-basic-static/public/favicon.ico
new file mode 100644
index 0000000000..1a1751676f
Binary files /dev/null and b/examples/react/start-basic-static/public/favicon.ico differ
diff --git a/examples/react/start-basic-static/public/favicon.png b/examples/react/start-basic-static/public/favicon.png
new file mode 100644
index 0000000000..1e77bc0609
Binary files /dev/null and b/examples/react/start-basic-static/public/favicon.png differ
diff --git a/examples/react/start-basic-static/public/site.webmanifest b/examples/react/start-basic-static/public/site.webmanifest
new file mode 100644
index 0000000000..fa99de77db
--- /dev/null
+++ b/examples/react/start-basic-static/public/site.webmanifest
@@ -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"
+}
diff --git a/examples/react/start-basic-static/tailwind.config.cjs b/examples/react/start-basic-static/tailwind.config.cjs
new file mode 100644
index 0000000000..75fe25dbf7
--- /dev/null
+++ b/examples/react/start-basic-static/tailwind.config.cjs
@@ -0,0 +1,4 @@
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+ content: ['./app/**/*.{js,ts,jsx,tsx}'],
+}
diff --git a/examples/react/start-basic-static/tsconfig.json b/examples/react/start-basic-static/tsconfig.json
new file mode 100644
index 0000000000..d1b5b77660
--- /dev/null
+++ b/examples/react/start-basic-static/tsconfig.json
@@ -0,0 +1,22 @@
+{
+ "include": ["**/*.ts", "**/*.tsx"],
+ "compilerOptions": {
+ "strict": true,
+ "esModuleInterop": true,
+ "jsx": "react-jsx",
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "lib": ["DOM", "DOM.Iterable", "ES2022"],
+ "isolatedModules": true,
+ "resolveJsonModule": true,
+ "skipLibCheck": true,
+ "target": "ES2022",
+ "allowJs": true,
+ "forceConsistentCasingInFileNames": true,
+ "baseUrl": ".",
+ "paths": {
+ "~/*": ["./app/*"]
+ },
+ "noEmit": true
+ }
+}
diff --git a/examples/react/start-basic/app/routes/deferred.tsx b/examples/react/start-basic/app/routes/deferred.tsx
index 7cc7f8b9b5..606ab69b80 100644
--- a/examples/react/start-basic/app/routes/deferred.tsx
+++ b/examples/react/start-basic/app/routes/deferred.tsx
@@ -3,13 +3,13 @@ import { createServerFn } from '@tanstack/start'
import { Suspense, useState } from 'react'
const personServerFn = createServerFn({ method: 'GET' })
- .validator((d) => d as string)
+ .validator((d: string) => d)
.handler(({ data: name }) => {
return { name, randomNumber: Math.floor(Math.random() * 100) }
})
const slowServerFn = createServerFn({ method: 'GET' })
- .validator((d) => d as string)
+ .validator((d: string) => d)
.handler(async ({ data: name }) => {
await new Promise((r) => setTimeout(r, 1000))
return { name, randomNumber: Math.floor(Math.random() * 100) }
diff --git a/examples/react/start-basic/app/utils/posts.tsx b/examples/react/start-basic/app/utils/posts.tsx
index 196498f8c9..99e43f5197 100644
--- a/examples/react/start-basic/app/utils/posts.tsx
+++ b/examples/react/start-basic/app/utils/posts.tsx
@@ -11,7 +11,7 @@ export type PostType = {
export const fetchPost = createServerFn({ method: 'GET' })
.middleware([logMiddleware])
- .validator((d) => d as string)
+ .validator((d: string) => d)
.handler(async ({ data }) => {
console.info(`Fetching post with id ${data}...`)
const post = await axios
diff --git a/examples/react/start-supabase-basic/app/utils/posts.ts b/examples/react/start-supabase-basic/app/utils/posts.ts
index 3c8b8d2434..6eb213af1f 100644
--- a/examples/react/start-supabase-basic/app/utils/posts.ts
+++ b/examples/react/start-supabase-basic/app/utils/posts.ts
@@ -9,7 +9,7 @@ export type PostType = {
}
export const fetchPost = createServerFn({ method: 'GET' })
- .validator((d) => d as string)
+ .validator((d: string) => d)
.handler(async ({ data: postId }) => {
console.info(`Fetching post with id ${postId}...`)
const post = await axios
diff --git a/examples/react/with-framer-motion/src/main.tsx b/examples/react/with-framer-motion/src/main.tsx
index 3ce3a89ea1..e636ddca43 100644
--- a/examples/react/with-framer-motion/src/main.tsx
+++ b/examples/react/with-framer-motion/src/main.tsx
@@ -35,7 +35,6 @@ const fetchPost = async (postId: string) => {
const post = await axios
.get(`https://jsonplaceholder.typicode.com/posts/${postId}`)
.then((r) => r.data)
- .catch(console.log)
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!post) {
diff --git a/packages/start/src/client-runtime/fetcher.tsx b/packages/start/src/client-runtime/fetcher.tsx
index 70eea4b9ae..ce00aab5a5 100644
--- a/packages/start/src/client-runtime/fetcher.tsx
+++ b/packages/start/src/client-runtime/fetcher.tsx
@@ -5,7 +5,7 @@ import {
isPlainObject,
isRedirect,
} from '@tanstack/react-router'
-import type { MiddlewareOptions } from '../client/createServerFn'
+import type { MiddlewareCtx } from '../client/createServerFn'
export async function fetcher(
base: string,
@@ -17,7 +17,7 @@ export async function fetcher(
// If createServerFn was used to wrap the fetcher,
// We need to handle the arguments differently
if (isPlainObject(_first) && _first.method) {
- const first = _first as MiddlewareOptions
+ const first = _first as MiddlewareCtx
const type = first.data instanceof FormData ? 'formData' : 'payload'
// Arrange the headers
@@ -30,7 +30,7 @@ export async function fetcher(
: {}),
...(first.headers instanceof Headers
? Object.fromEntries(first.headers.entries())
- : first.headers || {}),
+ : first.headers),
})
// If the method is GET, we need to move the payload to the query string
@@ -40,6 +40,7 @@ export async function fetcher(
payload: defaultTransformer.stringify({
data: first.data,
context: first.context,
+ ...(typeof first.type !== 'function' && { type: first.type }),
}),
})
@@ -101,7 +102,7 @@ export async function fetcher(
}
}
-function getFetcherRequestOptions(opts: MiddlewareOptions) {
+function getFetcherRequestOptions(opts: MiddlewareCtx) {
if (opts.method === 'POST') {
if (opts.data instanceof FormData) {
opts.data.set('__TSR_CONTEXT', defaultTransformer.stringify(opts.context))
diff --git a/packages/start/src/client/createMiddleware.ts b/packages/start/src/client/createMiddleware.ts
index 6eefb7751f..762310b130 100644
--- a/packages/start/src/client/createMiddleware.ts
+++ b/packages/start/src/client/createMiddleware.ts
@@ -1,4 +1,8 @@
-import type { ConstrainValidator, Method } from './createServerFn'
+import type {
+ ConstrainValidator,
+ Method,
+ ServerFnTypeOrTypeFn,
+} from './createServerFn'
import type {
Assign,
Constrain,
@@ -156,6 +160,7 @@ export interface MiddlewareClientFnOptions<
sendContext?: unknown // cc Chris Horobin
method: Method
next: MiddlewareClientNextFn
+ type: ServerFnTypeOrTypeFn
}
export type MiddlewareClientFn<
diff --git a/packages/start/src/client/createServerFn.ts b/packages/start/src/client/createServerFn.ts
index 6408e783cd..a772fb4676 100644
--- a/packages/start/src/client/createServerFn.ts
+++ b/packages/start/src/client/createServerFn.ts
@@ -1,5 +1,4 @@
-import invariant from 'tiny-invariant'
-import { defaultTransformer } from '@tanstack/react-router'
+import { defaultTransformer, invariant, warning } from '@tanstack/react-router'
import { mergeHeaders } from './headers'
import { globalMiddleware } from './registerGlobalMiddleware'
import type {
@@ -53,8 +52,11 @@ export type FetcherImpl =
export type FetcherBaseOptions = {
headers?: HeadersInit
+ type?: ServerFnType
}
+export type ServerFnType = 'static' | 'dynamic'
+
export interface RequiredFetcherDataOptions extends FetcherBaseOptions {
data: TInput
}
@@ -86,11 +88,11 @@ export interface ServerFnCtx {
}
export type CompiledFetcherFn = {
- (opts: CompiledFetcherFnOptions & ServerFnBaseOptions): Promise
+ (opts: CompiledFetcherFnOptions & ServerFnInternalOptions): Promise
url: string
}
-type ServerFnBaseOptions<
+type ServerFnInternalOptions<
TMethod extends Method = 'GET',
TResponse = unknown,
TMiddlewares = unknown,
@@ -104,6 +106,7 @@ type ServerFnBaseOptions<
serverFn?: ServerFn
filename: string
functionId: string
+ type: ServerFnTypeOrTypeFn
}
export type ConstrainValidator = unknown extends TValidator
@@ -119,6 +122,22 @@ export type ConstrainValidator = unknown extends TValidator
>
>
+// Validator
+export interface ServerFnValidator {
+ validator: (
+ validator: ConstrainValidator,
+ ) => ServerFnAfterValidator
+}
+
+export interface ServerFnAfterValidator<
+ TMethod extends Method,
+ TMiddlewares,
+ TValidator,
+> extends ServerFnMiddleware,
+ ServerFnTyper,
+ ServerFnHandler {}
+
+// Middleware
export interface ServerFnMiddleware {
middleware: (
middlewares: Constrain>,
@@ -130,21 +149,35 @@ export interface ServerFnAfterMiddleware<
TMiddlewares,
TValidator,
> extends ServerFnValidator,
+ ServerFnTyper,
ServerFnHandler {}
-export interface ServerFnValidator {
- validator: (
- validator: ConstrainValidator,
- ) => ServerFnAfterValidator
+// Typer
+export interface ServerFnTyper<
+ TMethod extends Method,
+ TMiddlewares,
+ TValidator,
+> {
+ type: (
+ typer: ServerFnTypeOrTypeFn,
+ ) => ServerFnAfterTyper
}
-export interface ServerFnAfterValidator<
+export type ServerFnTypeOrTypeFn<
TMethod extends Method,
TMiddlewares,
TValidator,
-> extends ServerFnMiddleware,
- ServerFnHandler {}
+> =
+ | ServerFnType
+ | ((ctx: ServerFnCtx) => ServerFnType)
+export interface ServerFnAfterTyper<
+ TMethod extends Method,
+ TMiddlewares,
+ TValidator,
+> extends ServerFnHandler {}
+
+// Handler
export interface ServerFnHandler<
TMethod extends Method,
TMiddlewares,
@@ -160,12 +193,145 @@ export interface ServerFnBuilder<
TResponse = unknown,
TMiddlewares = unknown,
TValidator = unknown,
-> extends ServerFnMiddleware,
- ServerFnValidator,
+> extends ServerFnValidator,
+ ServerFnMiddleware,
+ ServerFnTyper,
ServerFnHandler {
- options: ServerFnBaseOptions
+ options: ServerFnInternalOptions
+}
+
+type StaticCachedResult = {
+ ctx?: {
+ result: any
+ context: any
+ }
+ error?: any
+}
+
+export type ServerFnStaticCache = {
+ getItem: (
+ ctx: MiddlewareCtx,
+ ) => StaticCachedResult | Promise
+ setItem: (ctx: MiddlewareCtx, response: StaticCachedResult) => Promise
+ fetchItem: (
+ ctx: MiddlewareCtx,
+ ) => StaticCachedResult | Promise
}
+let serverFnStaticCache: ServerFnStaticCache | undefined
+
+export function setServerFnStaticCache(
+ cache?: ServerFnStaticCache | (() => ServerFnStaticCache | undefined),
+) {
+ const previousCache = serverFnStaticCache
+ serverFnStaticCache = typeof cache === 'function' ? cache() : cache
+
+ return () => {
+ serverFnStaticCache = previousCache
+ }
+}
+
+export function createServerFnStaticCache(
+ serverFnStaticCache: ServerFnStaticCache,
+) {
+ return serverFnStaticCache
+}
+
+setServerFnStaticCache(() => {
+ const getStaticCacheUrl = (options: MiddlewareCtx, hash: string) => {
+ return `/__tsr/staticServerFnCache/${options.filename}__${options.functionId}__${hash}.json`
+ }
+
+ const jsonToFilenameSafeString = (json: any) => {
+ // Custom replacer to sort keys
+ const sortedKeysReplacer = (key: string, value: any) =>
+ value && typeof value === 'object' && !Array.isArray(value)
+ ? Object.keys(value)
+ .sort()
+ .reduce((acc: any, curr: string) => {
+ acc[curr] = value[curr]
+ return acc
+ }, {})
+ : value
+
+ // Convert JSON to string with sorted keys
+ const jsonString = JSON.stringify(json ?? '', sortedKeysReplacer)
+
+ // Replace characters invalid in filenames
+ return jsonString
+ .replace(/[/\\?%*:|"<>]/g, '-') // Replace invalid characters with a dash
+ .replace(/\s+/g, '_') // Optionally replace whitespace with underscores
+ }
+
+ const staticClientCache =
+ typeof document !== 'undefined' ? new Map() : null
+
+ return createServerFnStaticCache({
+ getItem: async (ctx) => {
+ if (typeof document === 'undefined') {
+ const hash = jsonToFilenameSafeString(ctx.data)
+ const url = getStaticCacheUrl(ctx, hash)
+ const publicUrl = process.env.TSS_OUTPUT_PUBLIC_DIR!
+
+ // Use fs instead of fetch to read from filesystem
+ const fs = await import('node:fs/promises')
+ const path = await import('node:path')
+ const filePath = path.join(publicUrl, url)
+
+ const [cachedResult, readError] = await fs
+ .readFile(filePath, 'utf-8')
+ .then((c) => [
+ defaultTransformer.parse(c) as {
+ ctx: unknown
+ error: any
+ },
+ null,
+ ])
+ .catch((e) => [null, e])
+
+ if (readError && readError.code !== 'ENOENT') {
+ throw readError
+ }
+
+ return cachedResult as StaticCachedResult
+ }
+ },
+ setItem: async (ctx, response) => {
+ const fs = await import('node:fs/promises')
+ const path = await import('node:path')
+
+ const hash = jsonToFilenameSafeString(ctx.data)
+ const url = getStaticCacheUrl(ctx, hash)
+ const publicUrl = process.env.TSS_OUTPUT_PUBLIC_DIR!
+ const filePath = path.join(publicUrl, url)
+
+ // Ensure the directory exists
+ await fs.mkdir(path.dirname(filePath), { recursive: true })
+
+ // Store the result with fs
+ await fs.writeFile(filePath, defaultTransformer.stringify(response))
+ },
+ fetchItem: async (ctx) => {
+ const hash = jsonToFilenameSafeString(ctx.data)
+ const url = getStaticCacheUrl(ctx, hash)
+
+ let result: any = staticClientCache?.get(url)
+
+ if (!result) {
+ result = await fetch(url, {
+ method: 'GET',
+ })
+ .then((r) => r.text())
+ .then((d) => defaultTransformer.parse(d))
+
+ staticClientCache?.set(url, result)
+ }
+
+ return result
+ },
+ })
+})
+
export function createServerFn<
TMethod extends Method,
TResponse = unknown,
@@ -173,11 +339,16 @@ export function createServerFn<
TValidator = undefined,
>(
options?: {
- method: TMethod
+ method?: TMethod
},
- __opts?: ServerFnBaseOptions,
+ __opts?: ServerFnInternalOptions<
+ TMethod,
+ TResponse,
+ TMiddlewares,
+ TValidator
+ >,
): ServerFnBuilder {
- const resolvedOptions = (__opts || options || {}) as ServerFnBaseOptions<
+ const resolvedOptions = (__opts || options || {}) as ServerFnInternalOptions<
TMethod,
TResponse,
TMiddlewares,
@@ -202,6 +373,12 @@ export function createServerFn<
Object.assign(resolvedOptions, { validator }),
) as any
},
+ type: (type) => {
+ return createServerFn(
+ undefined,
+ Object.assign(resolvedOptions, { type }),
+ ) as any
+ },
handler: (...args) => {
// This function signature changes due to AST transformations
// in the babel plugin. We need to cast it to the correct
@@ -219,11 +396,6 @@ export function createServerFn<
serverFn,
})
- invariant(
- extractedFn.url,
- `createServerFn must be called with a function that is marked with the 'use server' pragma. Are you using the @tanstack/start-vite-plugin ?`,
- )
-
const resolvedMiddleware = [
...(resolvedOptions.middleware || []),
serverFnBaseToMiddleware(resolvedOptions),
@@ -235,10 +407,8 @@ export function createServerFn<
async (opts?: CompiledFetcherFnOptions) => {
// Start by executing the client-side middleware chain
return executeMiddleware(resolvedMiddleware, 'client', {
- ...extractedFn,
- method: resolvedOptions.method,
- data: opts?.data as any,
- headers: opts?.headers,
+ ...resolvedOptions,
+ ...(opts as any),
context: Object.assign({}, extractedFn),
}).then((d) => d.result)
},
@@ -247,18 +417,76 @@ export function createServerFn<
...extractedFn,
// The extracted function on the server-side calls
// this function
- __executeServer: (opts: any) => {
- const parsedOpts =
- opts instanceof FormData ? extractFormDataContext(opts) : opts
-
- return executeMiddleware(resolvedMiddleware, 'server', {
- ...extractedFn,
- ...parsedOpts,
- }).then((d) => ({
- // Only send the result and sendContext back to the client
- result: d.result,
- context: d.sendContext,
- }))
+ __executeServer: async (opts: any) => {
+ if (process.env.ROUTER !== 'client') {
+ const parsedOpts =
+ opts instanceof FormData ? extractFormDataContext(opts) : opts
+
+ const ctx = {
+ ...resolvedOptions,
+ ...parsedOpts,
+ }
+
+ ctx.type =
+ typeof resolvedOptions.type === 'function'
+ ? resolvedOptions.type(ctx)
+ : resolvedOptions.type
+
+ const run = async () =>
+ executeMiddleware(resolvedMiddleware, 'server', ctx).then(
+ (d) => ({
+ // Only send the result and sendContext back to the client
+ result: d.result,
+ context: d.sendContext,
+ }),
+ )
+
+ if (ctx.type === 'static') {
+ let response: StaticCachedResult | undefined
+
+ // If we can get the cached item, try to get it
+ if (serverFnStaticCache?.getItem) {
+ // If this throws, it's okay to let it bubble up
+ response = await serverFnStaticCache.getItem(ctx)
+ }
+
+ if (!response) {
+ // If there's no cached item, execute the server function
+ response = await run()
+ .then((d) => {
+ return {
+ ctx: d,
+ error: null,
+ }
+ })
+ .catch((e) => {
+ return {
+ ctx: undefined,
+ error: e,
+ }
+ })
+
+ if (serverFnStaticCache?.setItem) {
+ await serverFnStaticCache.setItem(ctx, response)
+ }
+ }
+
+ invariant(
+ response,
+ 'No response from both server and static cache!',
+ )
+
+ if (response.error) {
+ throw response.error
+ }
+
+ return response.ctx
+ }
+
+ return run()
+ }
+
+ return undefined
},
},
) as any
@@ -283,7 +511,7 @@ function extractFormDataContext(formData: FormData) {
context,
data: formData,
}
- } catch (e) {
+ } catch {
return {
data: formData,
}
@@ -314,19 +542,16 @@ function flattenMiddlewares(
return flattened
}
-export type MiddlewareOptions = {
- method: Method
- data: any
- headers?: HeadersInit
- sendContext?: any
- context?: any
-}
-
-export type MiddlewareResult = {
+export type MiddlewareCtx = {
context: any
sendContext: any
data: any
result: unknown
+ method: Method
+ type: 'static' | 'dynamic'
+ headers: HeadersInit
+ filename: string
+ functionId: string
}
const applyMiddleware = (
@@ -335,14 +560,11 @@ const applyMiddleware = (
| AnyMiddleware['options']['server']
| AnyMiddleware['options']['clientAfter']
>,
- mCtx: MiddlewareOptions,
- nextFn: (ctx: MiddlewareOptions) => Promise,
+ mCtx: MiddlewareCtx,
+ nextFn: (ctx: MiddlewareCtx) => Promise,
) => {
return middlewareFn({
- data: mCtx.data,
- context: mCtx.context,
- sendContext: mCtx.sendContext,
- method: mCtx.method,
+ ...mCtx,
next: ((userResult: any) => {
// Take the user provided context
// and merge it with the current context
@@ -360,17 +582,14 @@ const applyMiddleware = (
// Return the next middleware
return nextFn({
- method: mCtx.method,
- data: mCtx.data,
+ ...mCtx,
context,
sendContext,
headers,
result: userResult?.result ?? (mCtx as any).result,
- } as MiddlewareResult & {
- method: Method
})
}) as any,
- })
+ } as any)
}
function execValidator(validator: AnyValidator, input: unknown): unknown {
@@ -402,14 +621,14 @@ function execValidator(validator: AnyValidator, input: unknown): unknown {
async function executeMiddleware(
middlewares: Array,
env: 'client' | 'server',
- opts: MiddlewareOptions,
-): Promise {
+ ctx: Omit & { headers?: HeadersInit },
+): Promise {
const flattenedMiddlewares = flattenMiddlewares([
...globalMiddleware,
...middlewares,
])
- const next = async (ctx: MiddlewareOptions): Promise => {
+ const next = async (ctx: MiddlewareCtx): Promise => {
// Get the next middleware
const nextMiddleware = flattenedMiddlewares.shift()
@@ -436,15 +655,15 @@ async function executeMiddleware(
return applyMiddleware(
middlewareFn,
ctx,
- async (userCtx): Promise => {
+ async (userCtx): Promise => {
// If there is a clientAfter function and we are on the client
if (env === 'client' && nextMiddleware.options.clientAfter) {
// We need to await the next middleware and get the result
- const result = await next(userCtx)
+ const resultCtx = await next(userCtx)
// Then we can execute the clientAfter function
return applyMiddleware(
nextMiddleware.options.clientAfter,
- result as any,
+ resultCtx as any,
// Identity, because there "next" is just returning
(d: any) => d,
) as any
@@ -460,15 +679,15 @@ async function executeMiddleware(
// Start the middleware chain
return next({
- ...opts,
- headers: opts.headers || {},
- sendContext: (opts as any).sendContext || {},
- context: opts.context || {},
+ ...ctx,
+ headers: ctx.headers || {},
+ sendContext: (ctx as any).sendContext || {},
+ context: ctx.context || {},
})
}
function serverFnBaseToMiddleware(
- options: ServerFnBaseOptions,
+ options: ServerFnInternalOptions,
): AnyMiddleware {
return {
_types: undefined!,
@@ -476,23 +695,52 @@ function serverFnBaseToMiddleware(
validator: options.validator,
validateClient: options.validateClient,
client: async ({ next, sendContext, ...ctx }) => {
- // Execute the extracted function
- // but not before serializing the context
- const res = await options.extractedFn?.({
+ const payload = {
...ctx,
// switch the sendContext over to context
context: sendContext,
- } as any)
+ type: typeof ctx.type === 'function' ? ctx.type(ctx) : ctx.type,
+ } as any
+
+ if (
+ ctx.type === 'static' &&
+ process.env.NODE_ENV === 'production' &&
+ typeof document !== 'undefined' &&
+ serverFnStaticCache?.fetchItem
+ ) {
+ const result = await serverFnStaticCache.fetchItem(payload)
+
+ if (result) {
+ if (result.error) {
+ throw result.error
+ }
+
+ return next(result.ctx)
+ }
+
+ warning(
+ result,
+ `No static cache item found for ${payload.filename}__${payload.functionId}__${JSON.stringify(payload.data)}, falling back to server function...`,
+ )
+ }
+
+ // Execute the extracted function
+ // but not before serializing the context
+ const res = await options.extractedFn?.(payload)
return next(res)
},
server: async ({ next, ...ctx }) => {
- // Execute the server function
- const result = await options.serverFn?.(ctx as any)
+ if (process.env.ROUTER !== 'client') {
+ // Execute the server function
+ const result = await options.serverFn?.(ctx as any)
+
+ return next({
+ result,
+ } as any)
+ }
- return next({
- result,
- } as any)
+ throw new Error('Server function called from the client!')
},
},
}
diff --git a/packages/start/src/client/index.tsx b/packages/start/src/client/index.tsx
index 08f8b6f2df..71807b74e2 100644
--- a/packages/start/src/client/index.tsx
+++ b/packages/start/src/client/index.tsx
@@ -21,6 +21,7 @@ export {
type FetcherBaseOptions,
type ServerFn,
type ServerFnCtx,
+ type MiddlewareCtx,
} from './createServerFn'
export {
createMiddleware,
diff --git a/packages/start/src/client/tests/createServerFn.test-d.tsx b/packages/start/src/client/tests/createServerFn.test-d.tsx
index b2736e359d..0c1d18828d 100644
--- a/packages/start/src/client/tests/createServerFn.test-d.tsx
+++ b/packages/start/src/client/tests/createServerFn.test-d.tsx
@@ -47,6 +47,7 @@ test('createServerFn with validator', () => {
expectTypeOf(fn).parameter(0).toEqualTypeOf<{
data: { input: string }
headers?: HeadersInit
+ type?: 'static' | 'dynamic'
}>()
})
@@ -147,6 +148,7 @@ test('createServerFn with middleware and validator', () => {
readonly inputC: 'inputC'
}
headers?: HeadersInit
+ type?: 'static' | 'dynamic'
}>()
expectTypeOf(fn).returns.resolves.toEqualTypeOf<'data'>()
@@ -258,6 +260,7 @@ test('createServerFn where validator is optional if object is optional', () => {
| {
data?: 'c' | undefined
headers?: HeadersInit
+ type?: 'static' | 'dynamic'
}
| undefined
>()
@@ -276,6 +279,7 @@ test('createServerFn where data is optional if there is no validator', () => {
| {
data?: undefined
headers?: HeadersInit
+ type?: 'static' | 'dynamic'
}
| undefined
>()
diff --git a/packages/start/src/config/index.ts b/packages/start/src/config/index.ts
index f371997c8e..1b581239b2 100644
--- a/packages/start/src/config/index.ts
+++ b/packages/start/src/config/index.ts
@@ -18,6 +18,7 @@ import { config } from 'vinxi/plugins/config'
import { serverFunctions } from '@vinxi/server-functions/plugin'
// @ts-expect-error
import { serverTransform } from '@vinxi/server-functions/server'
+import { createNitro } from 'nitropack'
import { tanstackStartVinxiFileRouter } from './vinxi-file-router.js'
import {
checkDeploymentPresetInput,
@@ -88,9 +89,9 @@ function mergeSsrOptions(options: Array) {
return ssrOptions
}
-export function defineConfig(
+export async function defineConfig(
inlineConfig: TanStackStartInputConfig = {},
-): VinxiApp {
+): Promise {
const opts = inlineConfigSchema.parse(inlineConfig)
const { preset: configDeploymentPreset, ...serverOptions } =
@@ -123,6 +124,14 @@ export function defineConfig(
const apiEntryExists = existsSync(apiEntry)
+ // Create a dummy nitro app to get the resolved public output path
+ const dummyNitroApp = await createNitro({
+ preset: deploymentPreset,
+ })
+
+ const nitroOutputPublicDir = dummyNitroApp.options.output.publicDir
+ await dummyNitroApp.close()
+
let vinxiApp = createApp({
server: {
...serverOptions,
@@ -162,10 +171,15 @@ export function defineConfig(
define: {
...(viteConfig.userConfig.define || {}),
...(clientViteConfig.userConfig.define || {}),
+ ...injectDefineEnv('ROUTER_BASE', 'client'),
...injectDefineEnv('TSS_PUBLIC_BASE', publicBase),
...injectDefineEnv('TSS_CLIENT_BASE', clientBase),
...injectDefineEnv('TSS_SERVER_BASE', serverBase),
...injectDefineEnv('TSS_API_BASE', apiBase),
+ ...injectDefineEnv(
+ 'TSS_OUTPUT_PUBLIC_DIR',
+ nitroOutputPublicDir,
+ ),
},
}),
...(viteConfig.plugins || []),
@@ -199,10 +213,15 @@ export function defineConfig(
define: {
...(viteConfig.userConfig.define || {}),
...(ssrViteConfig.userConfig.define || {}),
+ ...injectDefineEnv('ROUTER_BASE', 'ssr'),
...injectDefineEnv('TSS_PUBLIC_BASE', publicBase),
...injectDefineEnv('TSS_CLIENT_BASE', clientBase),
...injectDefineEnv('TSS_SERVER_BASE', serverBase),
...injectDefineEnv('TSS_API_BASE', apiBase),
+ ...injectDefineEnv(
+ 'TSS_OUTPUT_PUBLIC_DIR',
+ nitroOutputPublicDir,
+ ),
},
}),
tsrRoutesManifest({
@@ -248,10 +267,15 @@ export function defineConfig(
define: {
...(viteConfig.userConfig.define || {}),
...(serverViteConfig.userConfig.define || {}),
+ ...injectDefineEnv('ROUTER_BASE', 'server'),
...injectDefineEnv('TSS_PUBLIC_BASE', publicBase),
...injectDefineEnv('TSS_CLIENT_BASE', clientBase),
...injectDefineEnv('TSS_SERVER_BASE', serverBase),
...injectDefineEnv('TSS_API_BASE', apiBase),
+ ...injectDefineEnv(
+ 'TSS_OUTPUT_PUBLIC_DIR',
+ nitroOutputPublicDir,
+ ),
},
}),
serverFunctions.server({
@@ -313,10 +337,12 @@ export function defineConfig(
define: {
...(viteConfig.userConfig.define || {}),
...(apiViteConfig.userConfig.define || {}),
+ ...injectDefineEnv('ROUTER_BASE', 'api'),
...injectDefineEnv('TSS_PUBLIC_BASE', publicBase),
...injectDefineEnv('TSS_CLIENT_BASE', clientBase),
...injectDefineEnv('TSS_SERVER_BASE', serverBase),
...injectDefineEnv('TSS_API_BASE', apiBase),
+ ...injectDefineEnv('TSS_OUTPUT_PUBLIC_DIR', nitroOutputPublicDir),
},
}),
TanStackRouterVite({
diff --git a/packages/start/src/react-server-runtime/index.tsx b/packages/start/src/react-server-runtime/index.tsx
index d7fbea3fe7..6c083c77f8 100644
--- a/packages/start/src/react-server-runtime/index.tsx
+++ b/packages/start/src/react-server-runtime/index.tsx
@@ -10,5 +10,7 @@ export function createServerReference(
return Object.assign(fn, {
url: functionUrl,
+ filename: id,
+ functionId: name,
})
}
diff --git a/packages/start/src/server-runtime/index.tsx b/packages/start/src/server-runtime/index.tsx
index eda1e96e91..84a0759e6d 100644
--- a/packages/start/src/server-runtime/index.tsx
+++ b/packages/start/src/server-runtime/index.tsx
@@ -152,7 +152,9 @@ export function createServerReference(_fn: any, id: string, name: string) {
}
return Object.assign(proxyFn, {
- url: functionUrl.replace(fakeHost, ''),
+ url: functionUrl
+ .replace(fakeHost, '')
+ .replace(fakeHost.replace('http:', 'https:'), ''),
filename: id,
functionId: name,
})
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2c470e9231..bfcad36ac8 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1924,10 +1924,10 @@ importers:
version: 18.3.1
html-webpack-plugin:
specifier: ^5.6.3
- version: 5.6.3(@rspack/core@1.1.5(@swc/helpers@0.5.15))(webpack@5.97.0)
+ version: 5.6.3(@rspack/core@1.1.5(@swc/helpers@0.5.15))(webpack@5.97.0(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4))
swc-loader:
specifier: ^0.2.6
- version: 0.2.6(@swc/core@1.9.3(@swc/helpers@0.5.15))(webpack@5.97.0)
+ version: 0.2.6(@swc/core@1.9.3(@swc/helpers@0.5.15))(webpack@5.97.0(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4))
typescript:
specifier: ^5.7.2
version: 5.7.2
@@ -2613,6 +2613,110 @@ importers:
specifier: ^5.1.3
version: 5.1.3(typescript@5.7.2)(vite@6.0.3(@types/node@22.10.1)(jiti@2.4.1)(terser@5.36.0)(tsx@4.19.2)(yaml@2.6.1))
+ examples/react/start-basic-static:
+ dependencies:
+ '@tanstack/react-router':
+ specifier: workspace:*
+ version: link:../../../packages/react-router
+ '@tanstack/router-devtools':
+ specifier: workspace:*
+ version: link:../../../packages/router-devtools
+ '@tanstack/start':
+ specifier: workspace:*
+ version: link:../../../packages/start
+ react:
+ specifier: ^18.3.1
+ version: 18.3.1
+ react-dom:
+ specifier: ^18.3.1
+ version: 18.3.1(react@18.3.1)
+ redaxios:
+ specifier: ^0.5.1
+ version: 0.5.1
+ tailwind-merge:
+ specifier: ^2.5.5
+ version: 2.5.5
+ vinxi:
+ specifier: 0.4.3
+ version: 0.4.3(@types/node@22.10.1)(ioredis@5.4.1)(terser@5.36.0)(typescript@5.7.2)
+ devDependencies:
+ '@types/node':
+ specifier: ^22.5.4
+ version: 22.10.1
+ '@types/react':
+ specifier: ^18.2.65
+ version: 18.3.12
+ '@types/react-dom':
+ specifier: ^18.2.21
+ version: 18.3.1
+ autoprefixer:
+ specifier: ^10.4.20
+ version: 10.4.20(postcss@8.4.49)
+ postcss:
+ specifier: ^8.4.49
+ version: 8.4.49
+ tailwindcss:
+ specifier: ^3.4.15
+ version: 3.4.16
+ typescript:
+ specifier: ^5.6.2
+ version: 5.7.2
+ vite-tsconfig-paths:
+ specifier: ^5.1.3
+ version: 5.1.3(typescript@5.7.2)(vite@6.0.3(@types/node@22.10.1)(jiti@2.4.1)(terser@5.36.0)(tsx@4.19.2)(yaml@2.6.1))
+
+ examples/react/start-basic-static:
+ dependencies:
+ '@tanstack/react-router':
+ specifier: workspace:*
+ version: link:../../../packages/react-router
+ '@tanstack/router-devtools':
+ specifier: workspace:*
+ version: link:../../../packages/router-devtools
+ '@tanstack/start':
+ specifier: workspace:*
+ version: link:../../../packages/start
+ react:
+ specifier: ^18.3.1
+ version: 18.3.1
+ react-dom:
+ specifier: ^18.3.1
+ version: 18.3.1(react@18.3.1)
+ redaxios:
+ specifier: ^0.5.1
+ version: 0.5.1
+ tailwind-merge:
+ specifier: ^2.5.5
+ version: 2.5.5
+ vinxi:
+ specifier: 0.4.3
+ version: 0.4.3(@types/node@22.10.1)(ioredis@5.4.1)(terser@5.36.0)(typescript@5.7.2)
+ devDependencies:
+ '@types/node':
+ specifier: ^22.5.4
+ version: 22.10.1
+ '@types/react':
+ specifier: ^18.2.65
+ version: 18.3.12
+ '@types/react-dom':
+ specifier: ^18.2.21
+ version: 18.3.1
+ autoprefixer:
+ specifier: ^10.4.20
+ version: 10.4.20(postcss@8.4.49)
+ postcss:
+ specifier: ^8.4.49
+ version: 8.4.49
+ tailwindcss:
+ specifier: ^3.4.15
+ version: 3.4.16
+ typescript:
+ specifier: ^5.6.2
+ version: 5.7.2
+ vite-tsconfig-paths:
+ specifier: ^5.1.3
+ version: 5.1.3(typescript@5.7.2)(vite@5.4.11(@types/node@22.10.1)(terser@5.36.0))
+
examples/react/start-clerk-basic:
dependencies:
'@clerk/tanstack-start':
@@ -3154,7 +3258,7 @@ importers:
version: 4.3.4(vite@6.0.3(@types/node@22.10.1)(jiti@2.4.1)(terser@5.36.0)(tsx@4.19.2)(yaml@2.6.1))
html-webpack-plugin:
specifier: ^5.6.0
- version: 5.6.3(@rspack/core@1.1.5(@swc/helpers@0.5.15))(webpack@5.97.0)
+ version: 5.6.3(@rspack/core@1.1.5(@swc/helpers@0.5.15))(webpack@5.97.0(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4))
react:
specifier: ^18.3.1
version: 18.3.1
@@ -3163,7 +3267,7 @@ importers:
version: 18.3.1(react@18.3.1)
swc-loader:
specifier: ^0.2.6
- version: 0.2.6(@swc/core@1.9.3(@swc/helpers@0.5.15))(webpack@5.97.0)
+ version: 0.2.6(@swc/core@1.9.3(@swc/helpers@0.5.15))(webpack@5.97.0(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4))
typescript:
specifier: ^5.7.2
version: 5.7.2
@@ -6971,6 +7075,14 @@ packages:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
+ crossws@0.2.4:
+ resolution: {integrity: sha512-DAxroI2uSOgUKLz00NX6A8U/8EE3SZHmIND+10jkVSaypvyt57J5JEOxAQOL6lQxyzi/wZbTIwssU1uy69h5Vg==}
+ peerDependencies:
+ uWebSockets.js: '*'
+ peerDependenciesMeta:
+ uWebSockets.js:
+ optional: true
+
crossws@0.3.1:
resolution: {integrity: sha512-HsZgeVYaG+b5zA+9PbIPGq4+J/CJynJuearykPsXx4V/eMhyQ5EDVg3Ak2FBZtVXCiOLu/U7IiwDHTr9MA+IKw==}
@@ -7910,6 +8022,9 @@ packages:
resolution: {integrity: sha512-O1Ld7Dr+nqPnmGpdhzLmMTQ4vAsD+rHwMm1NLUmoUFFymBOMKxCCrtDxqdBRYXdeEPEi3SyoR4TizJLQrnKBNA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ h3@1.11.1:
+ resolution: {integrity: sha512-AbaH6IDnZN6nmbnJOH72y3c5Wwh9P97soSVdGSBbcDACRdkC0FEWf25pzx4f/NuOCK6quHmW18yF2Wx+G4Zi1A==}
+
h3@1.13.0:
resolution: {integrity: sha512-vFEAu/yf8UMUcB4s43OaDaigcqpQd14yanmOsn+NcRX3/guSKncyE2rOYhq8RIchgJrPSs/QiIddnTTR1ddiAg==}
@@ -10545,6 +10660,10 @@ packages:
vfile@6.0.3:
resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}
+ vinxi@0.4.3:
+ resolution: {integrity: sha512-RgJz7RWftML5h/qfPsp3QKVc2FSlvV4+HevpE0yEY2j+PS/I2ULjoSsZDXaR8Ks2WYuFFDzQr8yrox7v8aqkng==}
+ hasBin: true
+
vinxi@0.5.1:
resolution: {integrity: sha512-jvl2hJ0fyWwfDVQdDDHCJiVxqU4k0A6kFAnljS0kIjrGfhdTvKEWIoj0bcJgMyrKhxNMoZZGmHZsstQgjDIL3g==}
hasBin: true
@@ -13566,17 +13685,17 @@ snapshots:
'@webassemblyjs/ast': 1.14.1
'@xtuc/long': 4.2.2
- '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4)(webpack@5.97.0)':
+ '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4(webpack-dev-server@5.1.0)(webpack@5.97.0))(webpack@5.97.0(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4))':
dependencies:
webpack: 5.97.0(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)
webpack-cli: 5.1.4(webpack-dev-server@5.1.0)(webpack@5.97.0)
- '@webpack-cli/info@2.0.2(webpack-cli@5.1.4)(webpack@5.97.0)':
+ '@webpack-cli/info@2.0.2(webpack-cli@5.1.4(webpack-dev-server@5.1.0)(webpack@5.97.0))(webpack@5.97.0(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4))':
dependencies:
webpack: 5.97.0(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)
webpack-cli: 5.1.4(webpack-dev-server@5.1.0)(webpack@5.97.0)
- '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4)(webpack-dev-server@5.1.0)(webpack@5.97.0)':
+ '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4(webpack-dev-server@5.1.0)(webpack@5.97.0))(webpack-dev-server@5.1.0(webpack-cli@5.1.4)(webpack@5.97.0))(webpack@5.97.0(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4))':
dependencies:
webpack: 5.97.0(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)
webpack-cli: 5.1.4(webpack-dev-server@5.1.0)(webpack@5.97.0)
@@ -14260,6 +14379,8 @@ snapshots:
shebang-command: 2.0.0
which: 2.0.2
+ crossws@0.2.4: {}
+
crossws@0.3.1:
dependencies:
uncrypto: 0.1.3
@@ -15432,6 +15553,21 @@ snapshots:
dependencies:
duplexer: 0.1.2
+ h3@1.11.1:
+ dependencies:
+ cookie-es: 1.2.2
+ crossws: 0.2.4
+ defu: 6.1.4
+ destr: 2.0.3
+ iron-webcrypto: 1.2.1
+ ohash: 1.1.4
+ radix3: 1.1.2
+ ufo: 1.5.4
+ uncrypto: 0.1.3
+ unenv: 1.10.0
+ transitivePeerDependencies:
+ - uWebSockets.js
+
h3@1.13.0:
dependencies:
cookie-es: 1.2.2
@@ -15520,7 +15656,7 @@ snapshots:
html-void-elements@3.0.0: {}
- html-webpack-plugin@5.6.3(@rspack/core@1.1.5(@swc/helpers@0.5.15))(webpack@5.97.0):
+ html-webpack-plugin@5.6.3(@rspack/core@1.1.5(@swc/helpers@0.5.15))(webpack@5.97.0(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)):
dependencies:
'@types/html-minifier-terser': 6.1.0
html-minifier-terser: 6.1.0
@@ -17666,7 +17802,7 @@ snapshots:
csso: 5.0.5
picocolors: 1.1.1
- swc-loader@0.2.6(@swc/core@1.9.3(@swc/helpers@0.5.15))(webpack@5.97.0):
+ swc-loader@0.2.6(@swc/core@1.9.3(@swc/helpers@0.5.15))(webpack@5.97.0(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)):
dependencies:
'@swc/core': 1.9.3(@swc/helpers@0.5.15)
'@swc/counter': 0.1.3
@@ -17736,19 +17872,19 @@ snapshots:
mkdirp: 1.0.4
yallist: 4.0.0
- terser-webpack-plugin@5.3.10(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack@5.97.0(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)):
+ terser-webpack-plugin@5.3.10(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack@5.97.0(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)):
dependencies:
'@jridgewell/trace-mapping': 0.3.25
jest-worker: 27.5.1
schema-utils: 3.3.0
serialize-javascript: 6.0.2
terser: 5.36.0
- webpack: 5.97.0(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)
+ webpack: 5.97.0(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)
optionalDependencies:
'@swc/core': 1.9.3(@swc/helpers@0.5.15)
esbuild: 0.24.0
- terser-webpack-plugin@5.3.10(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack@5.97.0):
+ terser-webpack-plugin@5.3.10(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack@5.97.0(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)):
dependencies:
'@jridgewell/trace-mapping': 0.3.25
jest-worker: 27.5.1
@@ -17760,6 +17896,18 @@ snapshots:
'@swc/core': 1.9.3(@swc/helpers@0.5.15)
esbuild: 0.24.0
+ terser-webpack-plugin@5.3.10(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack@5.97.0(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)):
+ dependencies:
+ '@jridgewell/trace-mapping': 0.3.25
+ jest-worker: 27.5.1
+ schema-utils: 3.3.0
+ serialize-javascript: 6.0.2
+ terser: 5.36.0
+ webpack: 5.97.0(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)
+ optionalDependencies:
+ '@swc/core': 1.9.3(@swc/helpers@0.5.15)
+ esbuild: 0.24.0
+
terser@5.36.0:
dependencies:
'@jridgewell/source-map': 0.3.6
@@ -18174,6 +18322,76 @@ snapshots:
'@types/unist': 3.0.3
vfile-message: 4.0.2
+ vinxi@0.4.3(@types/node@22.10.1)(ioredis@5.4.1)(terser@5.36.0)(typescript@5.7.2):
+ dependencies:
+ '@babel/core': 7.26.0
+ '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0)
+ '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.0)
+ '@types/micromatch': 4.0.9
+ '@vinxi/listhen': 1.5.6
+ boxen: 7.1.1
+ chokidar: 3.6.0
+ citty: 0.1.6
+ consola: 3.2.3
+ crossws: 0.2.4
+ dax-sh: 0.39.2
+ defu: 6.1.4
+ es-module-lexer: 1.5.4
+ esbuild: 0.20.2
+ fast-glob: 3.3.2
+ get-port-please: 3.1.2
+ h3: 1.11.1
+ hookable: 5.5.3
+ http-proxy: 1.18.1
+ micromatch: 4.0.8
+ nitropack: 2.10.4(typescript@5.7.2)
+ node-fetch-native: 1.6.4
+ path-to-regexp: 6.3.0
+ pathe: 1.1.2
+ radix3: 1.1.2
+ resolve: 1.22.8
+ serve-placeholder: 2.0.2
+ serve-static: 1.16.2
+ ufo: 1.5.4
+ unctx: 2.3.1
+ unenv: 1.10.0
+ unstorage: 1.13.1(ioredis@5.4.1)
+ vite: 5.4.11(@types/node@22.10.1)(terser@5.36.0)
+ zod: 3.23.8
+ transitivePeerDependencies:
+ - '@azure/app-configuration'
+ - '@azure/cosmos'
+ - '@azure/data-tables'
+ - '@azure/identity'
+ - '@azure/keyvault-secrets'
+ - '@azure/storage-blob'
+ - '@capacitor/preferences'
+ - '@electric-sql/pglite'
+ - '@libsql/client'
+ - '@netlify/blobs'
+ - '@planetscale/database'
+ - '@types/node'
+ - '@upstash/redis'
+ - '@vercel/kv'
+ - better-sqlite3
+ - debug
+ - drizzle-orm
+ - encoding
+ - idb-keyval
+ - ioredis
+ - less
+ - lightningcss
+ - mysql2
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+ - typescript
+ - uWebSockets.js
+ - xml2js
+
vinxi@0.5.1(@types/node@22.10.1)(ioredis@5.4.1)(jiti@2.4.1)(terser@5.36.0)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.6.1):
dependencies:
'@babel/core': 7.26.0
@@ -18412,9 +18630,9 @@ snapshots:
webpack-cli@5.1.4(webpack-dev-server@5.1.0)(webpack@5.97.0):
dependencies:
'@discoveryjs/json-ext': 0.5.7
- '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4)(webpack@5.97.0)
- '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4)(webpack@5.97.0)
- '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4)(webpack-dev-server@5.1.0)(webpack@5.97.0)
+ '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4(webpack-dev-server@5.1.0)(webpack@5.97.0))(webpack@5.97.0(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4))
+ '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4(webpack-dev-server@5.1.0)(webpack@5.97.0))(webpack@5.97.0(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4))
+ '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4(webpack-dev-server@5.1.0)(webpack@5.97.0))(webpack-dev-server@5.1.0(webpack-cli@5.1.4)(webpack@5.97.0))(webpack@5.97.0(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4))
colorette: 2.0.20
commander: 10.0.1
cross-spawn: 7.0.6
@@ -18428,7 +18646,7 @@ snapshots:
optionalDependencies:
webpack-dev-server: 5.1.0(webpack-cli@5.1.4)(webpack@5.97.0)
- webpack-dev-middleware@7.4.2(webpack@5.97.0):
+ webpack-dev-middleware@7.4.2(webpack@5.97.0(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)):
dependencies:
colorette: 2.0.20
memfs: 4.14.1
@@ -18467,7 +18685,7 @@ snapshots:
serve-index: 1.9.1
sockjs: 0.3.24
spdy: 4.0.2
- webpack-dev-middleware: 7.4.2(webpack@5.97.0)
+ webpack-dev-middleware: 7.4.2(webpack@5.97.0(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4))
ws: 8.18.0
optionalDependencies:
webpack: 5.97.0(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)
@@ -18540,7 +18758,7 @@ snapshots:
neo-async: 2.6.2
schema-utils: 3.3.0
tapable: 2.2.1
- terser-webpack-plugin: 5.3.10(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack@5.97.0)
+ terser-webpack-plugin: 5.3.10(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack@5.97.0(@swc/core@1.9.3(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4))
watchpack: 2.4.2
webpack-sources: 3.2.3
optionalDependencies: