docs: reorganize plugin-first TanStack Start docs - #784
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
💡 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".
| 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> |
There was a problem hiding this comment.
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 👍 / 👎.
| { | ||
| entry: 'routes/home-page', | ||
| id: 'home', | ||
| path: '/site-notes', | ||
| }, |
There was a problem hiding this comment.
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 👍 / 👎.
| ```json title="plugins/blog/src/locales/en.json" | ||
| { | ||
| "cart": { | ||
| "greeting": "Hello, {name}!", |
There was a problem hiding this comment.
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 👍 / 👎.
| href: '/admin/site-notes/settings', | ||
| icon: <SettingsIcon />, | ||
| id: 'settings', | ||
| permission: { module: 'site_notes', permission: 'can_manage_settings' }, | ||
| }, |
There was a problem hiding this comment.
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 👍 / 👎.
| <ContentDataTable | ||
| columns={columns} | ||
| data={loaderData} | ||
| defaultPageSize={search.first} | ||
| /> |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
💡 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', |
There was a problem hiding this comment.
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 ++] |
There was a problem hiding this comment.
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' |
There was a problem hiding this comment.
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({ |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
💡 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 />} |
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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({ |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
💡 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 |
There was a problem hiding this comment.
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 👍 / 👎.
| The site key is public. A plugin page can read it with the shared TanStack Query | ||
| definition before rendering its form: |
There was a problem hiding this comment.
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 👍 / 👎.
| icon: <BarChart3Icon />, | ||
| id: 'stats', |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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', |
There was a problem hiding this comment.
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 ++] |
There was a problem hiding this comment.
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 👍 / 👎.
Summary
Verification
pnpm --filter web docs:checkpnpm --filter web exec fumadocs-mdxpnpm --filter web exec eslint src/docs/article.tsxgit diff --checkKnown baseline blockers
pnpm --filter web lintstill reports 5 existing source lint errors outside this documentation change.pnpm --filter web typecheckcannot resolve the workspace package build outputs in this checkout.