Skip to content

docs: reorganize plugin-first TanStack Start docs - #784

Open
aXenDeveloper wants to merge 4 commits into
canaryfrom
docs/plugin-first-tanstack-start
Open

docs: reorganize plugin-first TanStack Start docs#784
aXenDeveloper wants to merge 4 commits into
canaryfrom
docs/plugin-first-tanstack-start

Conversation

@aXenDeveloper

Copy link
Copy Markdown
Owner

Summary

  • move Start here to a plugin-first TanStack Start path, with deployment alongside setup
  • rewrite routes, APIs, AdminCP, dashboard widgets, content delivery, and tutorials around plugin-owned features
  • add concise Fumadocs cards, steps, code-diff highlights, SEO-ready metadata, image-prompt markers, and Bun/pnpm/npm command tabs
  • add a docs quality check for frontmatter, category metadata, and complete package-manager command variants

Verification

  • pnpm --filter web docs:check
  • pnpm --filter web exec fumadocs-mdx
  • pnpm --filter web exec eslint src/docs/article.tsx
  • git diff --check

Known baseline blockers

  • pnpm --filter web lint still reports 5 existing source lint errors outside this documentation change.
  • pnpm --filter web typecheck cannot resolve the workspace package build outputs in this checkout.

@vercel

vercel Bot commented Sep 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
vitnode-prod Ready Ready Preview Sep 3, 2026 7:55am UTC

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cd0ecd8520

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/web/src/docs/article.tsx Outdated
Building a page, API, AdminCP screen, or feature? Put it in a plugin.
Keep the host for composition and site-wide infrastructure; otherwise
it becomes a very expensive junk drawer.{' '}
<a href="/docs/dev/plugins/create">Create a plugin first.</a>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Route the callout link through the localized router

Use the Fumadocs/TanStack link component here instead of a raw anchor. On a localized documentation URL such as /pl/docs/dev/cache, this hard-coded href performs a full navigation to the unprefixed /docs/dev/plugins/create; unlike the MDX link mapping in src/docs/mdx-components.tsx, it bypasses the router rewrite and switches the reader back to the default locale.

Useful? React with 👍 / 👎.

Comment on lines +55 to +59
{
entry: 'routes/home-page',
id: 'home',
path: '/site-notes',
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Declare the tutorial's translation namespace

Add namespaces: ['@acme/site-notes'] to this route declaration. The next step calls useTranslations('@acme/site-notes'), but plugin routes that declare no namespaces inherit only the root core.global messages, so following this new tutorial produces missing-message output instead of the promised working translated page.

Useful? React with 👍 / 👎.

Comment on lines +63 to 66
```json title="plugins/blog/src/locales/en.json"
{
"cart": {
"greeting": "Hello, {name}!",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep plugin messages under the plugin id

After relocating this example to plugins/blog/src/locales/en.json, nest cart under @vitnode/blog and use the corresponding namespace. VitNode ignores plugin message roots outside the plugin id, so the shown useTranslations('cart') calls cannot resolve these messages when readers copy the plugin example.

Useful? React with 👍 / 👎.

Comment on lines +74 to +78
href: '/admin/site-notes/settings',
icon: <SettingsIcon />,
id: 'settings',
permission: { module: 'site_notes', permission: 'can_manage_settings' },
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Gate the AdminCP page content, not only its nav item

Add an AdminStaffPermissionGate or equivalent permission check to the page example. This permission field only filters the sidebar declaration; any authenticated AdminCP staff member can still navigate directly to /admin/site-notes/settings and render the page, despite the guide presenting can_manage_settings as the screen's access requirement.

Useful? React with 👍 / 👎.

Comment on lines +208 to +212
<ContentDataTable
columns={columns}
data={loaderData}
defaultPageSize={search.first}
/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Pass the documented table component its actual props

Replace this invocation with the real ContentDataTable contract. DataTableProps has no data or defaultPageSize properties and instead requires at least edges, pageInfo, id, and order (normally inside DataTableNavigationProvider), so the newly added frontend pagination example does not type-check or render when copied.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8f84a69452

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

publication: true,
editorial: true,
// [!code ++:13]
id: '@acme/site-notes.article',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use a valid content type identifier

defineContentType rejects this value because content IDs must match the lowercase dot-separated plugin.entity pattern; scoped package characters such as @ and / are not permitted. Anyone following this delivery example will fail immediately while defining the content type, and the same invalid ID is repeated in public-api-and-caching.mdx; use a valid ID such as acme.site-notes-article in both examples.

Useful? React with 👍 / 👎.

import { definePluginRoute } from '@vitnode/core/routing'

export const route = definePluginRoute({
load: async () => await fetchAnnouncements(), // [!code ++]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Seed the query that the screen reads

When this example is followed, the loader's response is stored only as plugin loaderData, while the screen ignores that value and starts a separate useSuspenseQuery; the loader therefore does not warm the advertised query key and SSR or navigation can fetch the announcements twice. Seed the same query entry before rendering, or pass the loader result to the query as initial data/read loaderData directly.

AGENTS.md reference: AGENTS.md:L44-L44

Useful? React with 👍 / 👎.

import { createIsomorphicFn } from '@tanstack/react-start'
import { clientModule, fetcherClient } from '@vitnode/core/lib/fetcher-client'
import { fetcher } from '@vitnode/core/tanstack/fetcher/server'
import type { usersModule } from '@vitnode/core/api/modules/users/users.module'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Import the server module as a runtime value

In the newly added isomorphic example, the .server() branch passes usersModule to fetcher, but this import type is erased during compilation, so copied code fails with usersModule being unavailable as a value. Import the real module normally for the server branch while retaining clientModule<typeof usersModule> for the browser reference.

AGENTS.md reference: AGENTS.md:L35-L35

Useful? React with 👍 / 👎.


export const Route = createFileRoute("/_main/blog/$slug")({
loader: async ({ params }) => {
export const route = definePluginRoute({

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Default-export the plugin page component

After converting this example from createFileRoute to definePluginRoute, it still places PostPage in a component option and shows no default export. Plugin route options only accept breadcrumb, head, load, and parseSearch; the runtime ignores component and then rejects the module because it has no default component, so the documented 404 route cannot load. Export PostPage as the module default instead.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5cdb07ae8d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

href="/docs/dev/plugins/admin"
/>
<Card
icon={<BookOpenIcon />}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Import the Content Engine card icon

BookOpenIcon is used here but omitted from the new lucide-react import. Because it is also absent from mdxComponents, rendering the main /docs/dev introduction resolves an undefined MDX component and fails instead of showing the page; add it to the import alongside the other card icons.

Useful? React with 👍 / 👎.

```

```tsx title="src/site/contact/contact-screen.tsx"
```tsx title="plugins/contact/src/routes/contact-page.tsx"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Default-export the converted captcha plugin page

In this newly converted plugin-route snippet, contact-page.tsx only exports the named ContactScreen. A manifest entry loading this module will be rejected because plugin page modules require their component as the default export, so readers following the example cannot mount the captcha page.

Useful? React with 👍 / 👎.

import { HOME_DESCRIPTION, HOME_TITLE } from '../views/metadata' // [!code ++]

export const Route = createFileRoute("/_main/")({
export const route = definePluginRoute({

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Return raw metadata from the plugin head

After converting this example to definePluginRoute, it still invokes the host-only pageHead helper, which is neither imported nor available inside an installable plugin. Plugin head should return { title, description } directly—the plugin-route mount already normalizes that object through the host's pageHead—otherwise copied code fails on the unresolved identifier.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cccf50e183

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


for (const block of blocks) {
const [, _info, body] = block
if (!/\b(?:bun|pnpm|npm)\b/.test(body)) continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restrict tab validation to installation commands

Limit this match to actual dependency-install commands. As written, any standalone shell block containing bun, pnpm, or npm—for example pnpm dev, npm run build, or even a comment mentioning npm—fails docs:check unless it has three package-manager variants, although the repository convention requires tabs specifically for install commands.

AGENTS.md reference: AGENTS.md:L76-L76

Useful? React with 👍 / 👎.

Comment on lines +134 to +135
The site key is public. A plugin page can read it with the shared TanStack Query
definition before rendering its form:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Warm the captcha configuration before rendering

Keep a loader-backed read for middlewareConfigQueryOptions() instead of reading it cold only from this plugin component. On the first visit, this useSuspenseQuery has no warmed cache entry, so the entire plugin page suspends while making an extra render-time request; the previous example explicitly used ensureQueryData in the route loader to avoid that behavior.

AGENTS.md reference: AGENTS.md:L44-L44

Useful? React with 👍 / 👎.

Comment on lines +54 to +55
icon: <BarChart3Icon />,
id: 'stats',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restore the dashboard widget title message

Add the corresponding locale entry when registering stats. resolveDashboardWidgets unconditionally translates @acme/site-notes.admin.dashboard.widgets.stats.title, but the rewritten guide removed the translation step and the generated Site Notes locale contains only home; opening the dashboard after following this example therefore produces missing-message output instead of a usable widget title.

Useful? React with 👍 / 👎.

component: StatsWidget,
// [!code ++:4]
permission: { module: 'site_notes', permission: 'can_view_stats' },
settingsComponent: StatsSettings,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Define the registered widget settings component

Define or import StatsSettings before registering it. The rewrite deletes the complete StatsSettings example but retains this reference, so readers following the advertised optional-settings step end up with an unresolved identifier and no example showing how settings are saved.

Useful? React with 👍 / 👎.

{
href: '/admin/site-notes/settings',
icon: <SettingsIcon />,
id: 'settings',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Add messages for the AdminCP navigation entry

Add the plugin title and navigation-label messages alongside this declaration. The AdminCP resolves this item through @acme/site-notes.admin.nav.settings and its group through @acme/site-notes.title; neither key exists in the generated Site Notes locale, and this rewrite removed the former localization step, so following the guide leaves the sidebar displaying missing-message identifiers.

Useful? React with 👍 / 👎.

onClick={() =>
void navigate({
resetScroll: false,
search: { page: search.page + 1 }, // [!code ++]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Initialize the catalog page search value

Declare a parseSearch handler or an eager searchEntry that defaults page before incrementing it. A plugin route with neither receives {} as its search value regardless of this TypeScript annotation, so opening /catalog without a query makes search.page undefined and the first click navigates with page: NaN instead of page 1.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

📖 Documentation Documentation changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant