tanstack-start

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

@json-render/tanstack-start

@json-render/tanstack-start

Use this integration when a TanStack Start app needs complete pages or routes described by json-render specs.
当 TanStack Start 应用需要由 json-render 规范描述完整页面或路由时,使用此集成。

Install

安装

bash
npm install @json-render/core @json-render/react @json-render/tanstack-start
bash
npm install @json-render/core @json-render/react @json-render/tanstack-start

Application Spec

应用规范

Include the server-safe built-in definitions in the generation catalog. The renderer supplies their component implementations.
typescript
import { defineCatalog } from "@json-render/core";
import {
  schema,
  startComponentDefinitions,
} from "@json-render/tanstack-start/server";

const catalog = defineCatalog(schema, {
  components: {
    ...startComponentDefinitions,
    Card: cardDefinition,
    Shell: shellDefinition,
    Navigation: navigationDefinition,
    Home: homeDefinition,
    Post: postDefinition,
  },
  actions: {},
});
Then use
StartAppSpec
and TanStack Router route patterns:
typescript
import type { StartAppSpec } from "@json-render/tanstack-start";

export const spec: StartAppSpec = {
  metadata: {
    title: { default: "Site", template: "%s | Site" },
  },
  layouts: {
    main: {
      root: "shell",
      elements: {
        shell: { type: "Shell", props: {}, children: ["nav", "slot"] },
        nav: { type: "Navigation", props: {}, children: [] },
        slot: { type: "Slot", props: {}, children: [] },
      },
    },
  },
  routes: {
    "/": {
      layout: "main",
      metadata: { title: "Home" },
      page: {
        root: "home",
        elements: {
          home: { type: "Home", props: {}, children: [] },
        },
      },
    },
    "/posts/$slug": {
      layout: "main",
      loader: "post",
      staticParams: [{ slug: "hello" }],
      page: {
        root: "post",
        elements: {
          post: {
            type: "Post",
            props: { value: { $state: "/post" } },
            children: [],
          },
        },
      },
    },
  },
};
Routes use
/posts/$slug
for named parameters and
/docs/$
for a splat. Splat loader parameters are slash-delimited strings under
_splat
. Escape route-key slashes as
~1
when generating RFC 6902 patches.
Every layout needs a
Slot
element. Declare
Slot
and
Link
through
startComponentDefinitions
; do not require consumers to register React implementations for them.
在生成目录中包含服务端安全的内置定义。渲染器提供它们的组件实现。
typescript
import { defineCatalog } from "@json-render/core";
import {
  schema,
  startComponentDefinitions,
} from "@json-render/tanstack-start/server";

const catalog = defineCatalog(schema, {
  components: {
    ...startComponentDefinitions,
    Card: cardDefinition,
    Shell: shellDefinition,
    Navigation: navigationDefinition,
    Home: homeDefinition,
    Post: postDefinition,
  },
  actions: {},
});
然后使用
StartAppSpec
和 TanStack Router 路由模式:
typescript
import type { StartAppSpec } from "@json-render/tanstack-start";

export const spec: StartAppSpec = {
  metadata: {
    title: { default: "Site", template: "%s | Site" },
  },
  layouts: {
    main: {
      root: "shell",
      elements: {
        shell: { type: "Shell", props: {}, children: ["nav", "slot"] },
        nav: { type: "Navigation", props: {}, children: [] },
        slot: { type: "Slot", props: {}, children: [] },
      },
    },
  },
  routes: {
    "/": {
      layout: "main",
      metadata: { title: "Home" },
      page: {
        root: "home",
        elements: {
          home: { type: "Home", props: {}, children: [] },
        },
      },
    },
    "/posts/$slug": {
      layout: "main",
      loader: "post",
      staticParams: [{ slug: "hello" }],
      page: {
        root: "post",
        elements: {
          post: {
            type: "Post",
            props: { value: { $state: "/post" } },
            children: [],
          },
        },
      },
    },
  },
};
路由使用
/posts/$slug
表示命名参数,
/docs/$
用于 splat 路由。Splat 加载器参数是
_splat
下以斜杠分隔的字符串。生成 RFC 6902 补丁时,将路由键中的斜杠转义为
~1
。
每个布局都需要一个
Slot
元素。通过
startComponentDefinitions
声明
Slot
和
Link
;无需让使用者注册它们的 React 实现。

Server Helpers

服务端辅助工具

typescript
import { createStartApp } from "@json-render/tanstack-start/server";

export const { getPageData, getHead, getStaticPaths } = createStartApp({
  spec,
  loaders: {
    post: async ({ slug }) => ({ post: await getPost(slug as string) }),
  },
});
State merge precedence is application state, layout state, page state, then loader data.
getHead
merges app and route metadata into TanStack
meta
and
links
descriptors.
getStaticPaths
includes static routes plus dynamic routes with
staticParams
. Convert its strings to
{ path }
objects for TanStack Start's top-level
pages
plugin option. Loader params are URL-decoded, while values from
staticParams
are URL-encoded in generated paths. Route matching treats trailing slashes as optional and accepts encoded or decoded pathname representations so loader data and metadata resolve the same static route.
typescript
import { createStartApp } from "@json-render/tanstack-start/server";

export const { getPageData, getHead, getStaticPaths } = createStartApp({
  spec,
  loaders: {
    post: async ({ slug }) => ({ post: await getPost(slug as string) }),
  },
});
状态合并优先级为:应用状态、布局状态、页面状态,最后是加载器数据。
getHead
将应用和路由元数据合并到 TanStack 的
meta
和
links
描述符中。
getStaticPaths
包含静态路由以及带有
staticParams
的动态路由。将其返回的字符串转换为
{ path }
对象,可用于 TanStack Start 的顶层
pages
插件选项。加载器参数是经过 URL 解码的,而
staticParams
中的值在生成的路径中是经过 URL 编码的。路由匹配将尾部斜杠视为可选,同时接受编码或解码后的路径名表示,因此加载器数据和元数据会解析到同一个静态路由。

Route Wiring

路由接入

tsx
import { createFileRoute, notFound } from "@tanstack/react-router";
import {
  PageRenderer,
  StartErrorBoundary,
  StartLoading,
  StartNotFound,
} from "@json-render/tanstack-start";
import { getHead, getPageData } from "@/lib/json-app";

export const Route = createFileRoute("/$")({
  loader: async ({ location }) => {
    const data = await getPageData({ pathname: location.pathname });
    if (!data) throw notFound();
    return data;
  },
  head: ({ match }) => getHead({ pathname: match.pathname }),
  component: () => <PageRenderer {...Route.useLoaderData()} />,
  pendingComponent: StartLoading,
  errorComponent: StartErrorBoundary,
  notFoundComponent: StartNotFound,
});
TanStack Router loaders run on both the server and client. If a spec factory or named loader uses credentials, database clients, or server-only imports, wrap
getPageData
and
getHead
in a TanStack Start
createServerFn
; do not import that server code directly into an isomorphic route loader.
tsx
import { createFileRoute, notFound } from "@tanstack/react-router";
import {
  PageRenderer,
  StartErrorBoundary,
  StartLoading,
  StartNotFound,
} from "@json-render/tanstack-start";
import { getHead, getPageData } from "@/lib/json-app";

export const Route = createFileRoute("/$")({
  loader: async ({ location }) => {
    const data = await getPageData({ pathname: location.pathname });
    if (!data) throw notFound();
    return data;
  },
  head: ({ match }) => getHead({ pathname: match.pathname }),
  component: () => <PageRenderer {...Route.useLoaderData()} />,
  pendingComponent: StartLoading,
  errorComponent: StartErrorBoundary,
  notFoundComponent: StartNotFound,
});
TanStack Router 加载器在服务端和客户端都会运行。如果规范工厂或命名加载器使用了凭据、数据库客户端或仅服务端导入的内容,请将
getPageData
和
getHead
包装在 TanStack Start 的
createServerFn
中;不要将该服务端代码直接导入到同构路由加载器中。

Root Provider

根 Provider

Wrap the root route's
Outlet
with
StartAppProvider
. Render
HeadContent
so metadata from
getHead
reaches the document.
tsx
import {
  createRootRoute,
  HeadContent,
  Outlet,
  Scripts,
} from "@tanstack/react-router";
import { StartAppProvider } from "@json-render/tanstack-start";
import { spec } from "@/lib/spec";

export const Route = createRootRoute({
  component: () => (
    <html lang="en">
      <head>
        <HeadContent />
      </head>
      <body>
        <StartAppProvider
          registry={registry}
          handlers={handlers}
          spec={spec}
        >
          <Outlet />
        </StartAppProvider>
        <Scripts />
      </body>
    </html>
  ),
});
Use
StartLoading
,
StartErrorBoundary
, and
StartNotFound
for TanStack Router's
pendingComponent
,
errorComponent
, and
notFoundComponent
options. When
StartAppProvider
receives
spec
, each component selects the matched route's corresponding fallback. Explicit fallback props override that lookup. Pass named
$computed
implementations through
StartAppProvider.functions
. The default error boundary invalidates the router and reruns a failed loader when the user selects Try again.
Import React components from
@json-render/tanstack-start
. Import
schema
,
createStartApp
,
matchRoute
,
resolveMetadata
, and static path helpers from
@json-render/tanstack-start/server
.
用
StartAppProvider
包裹根路由的
Outlet
。渲染
HeadContent
,以便
getHead
返回的元数据能作用于文档。
tsx
import {
  createRootRoute,
  HeadContent,
  Outlet,
  Scripts,
} from "@tanstack/react-router";
import { StartAppProvider } from "@json-render/tanstack-start";
import { spec } from "@/lib/spec";

export const Route = createRootRoute({
  component: () => (
    <html lang="en">
      <head>
        <HeadContent />
      </head>
      <body>
        <StartAppProvider
          registry={registry}
          handlers={handlers}
          spec={spec}
        >
          <Outlet />
        </StartAppProvider>
        <Scripts />
      </body>
    </html>
  ),
});
将
StartLoading
、
StartErrorBoundary
和
StartNotFound
用于 TanStack Router 的
pendingComponent
、
errorComponent
和
notFoundComponent
选项。当
StartAppProvider
接收到
spec
时,每个组件会选择匹配路由对应的回退内容。显式的回退属性会覆盖该查找逻辑。通过
StartAppProvider.functions
传递命名的
$computed
实现。当用户选择重试时,默认的错误边界会使路由失效并重新运行失败的加载器。
从
@json-render/tanstack-start
导入 React 组件。从
@json-render/tanstack-start/server
导入
schema
、
createStartApp
、
matchRoute
、
resolveMetadata
以及静态路径辅助函数。