Skip to content

feat(content): add public list pages to the Content Engine - #870

Closed
aXenDeveloper wants to merge 2 commits into
canaryfrom
claude/content-list-pages
Closed

aXenDeveloper wants to merge 2 commits into
canaryfrom
claude/content-list-pages

Conversation

@aXenDeveloper

Copy link
Copy Markdown
Owner

delivery.list gives a content type a public listing page, such as /blog,
with pagination, search and filters read from the URL:

  • defineContentType validates the list path (static, not under /admin or
    /api), the page size (1-50) and that every filter is in
    publicApi.filterableFields. The build fails with
    content-url-without-page when no page serves the list URL.
  • @vitnode/core/tanstack/content adds contentListSearch,
    contentListQuery, contentListPage and contentListPageHead, plus
    ContentList, ContentCard, ContentListEmpty, ContentListPagination,
    ContentListSearchForm and ContentListFilter.
  • Only the plain first page is indexed, with canonical and hreflang links.
    Later pages, searches and filters answer noindex, follow, a robots value
    plugin routes can now return. A page past the end answers 404.
  • The blog gets /blog and the example plugin /articles with a
    "Featured" filter.

Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01DChMEKwVTEBMNHcKKJ1T9u

claude added 2 commits October 9, 2026 22:30
`delivery.list` gives a content type a public listing page, such as `/blog`,
with pagination, search and filters read from the URL:

- `defineContentType` validates the list path (static, not under /admin or
  /api), the page size (1-50) and that every filter is in
  `publicApi.filterableFields`. The build fails with
  `content-url-without-page` when no page serves the list URL.
- `@vitnode/core/tanstack/content` adds `contentListSearch`,
  `contentListQuery`, `contentListPage` and `contentListPageHead`, plus
  `ContentList`, `ContentCard`, `ContentListEmpty`, `ContentListPagination`,
  `ContentListSearchForm` and `ContentListFilter`.
- Only the plain first page is indexed, with canonical and hreflang links.
  Later pages, searches and filters answer `noindex, follow`, a robots value
  plugin routes can now return. A page past the end answers 404.
- The blog gets `/blog` and the example plugin `/articles` with a
  "Featured" filter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DChMEKwVTEBMNHcKKJ1T9u
Adds "Show a list of records", a step-by-step guide that continues the
recipes example with `delivery.list`, a list page with search, filters and
pagination, and the SEO each URL gets. Screenshots are real captures of the
page built from the guide's code. "List page reference" lists every helper
and component, and the content type reference, the overview and the
delivery guide link to both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DChMEKwVTEBMNHcKKJ1T9u
@github-actions github-actions Bot added the 💡 Feature A new feature label Oct 10, 2026
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-10T07:26:10.228452Z 911dc6d PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

Copy link
Copy Markdown

React Doctor found 2 new issues in 2 files · 2 warnings · score 91 / 100 (Great) · 0 fixed · vs canary

2 warnings

src/content/delivery.ts

  • ⚠️ L336 Array lookup inside a loop js-set-map-lookups

src/tanstack/content/list.tsx

  • ⚠️ L319 preventDefault on a form or link no-prevent-default

Reviewed by React Doctor for commit 911dc6d. See inline comments for fixes.


const filters = names.map(name => {
const target = resolveSeoTarget(fields, name);
if (!publicApi.filterableFields.includes(name) || target === null) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

React Doctor · react-doctor/js-set-map-lookups (warning)

This scales poorly because array.includes() inside a loop scans the whole list every time. Use a Set for constant-time lookups.

Fix → Use a Set or Map when you check for the same items over and over. Array.includes/find scans the whole list each time

Docs

);

return (
<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.

React Doctor · react-doctor/no-prevent-default (warning)

Your users can't submit this

without JavaScript because onSubmit calls preventDefault(), so use a server action like <form action={serverAction}> to make it work either way.

Fix → Use <form action> where your framework supports it (it works without JS), or use a <button> instead of an <a> with preventDefault.

Docs

@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: 911dc6de4c

ℹ️ 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 on lines +73 to +74
load: async ({ context, search }) =>
await loadPosts({ locale: context.locale, search }),

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 Cache list loads through the route query client

On client navigation, this loader calls fetcher directly instead of warming data through context.queryClient.query, so identical list requests cannot be shared or deduplicated with preloads or other consumers and are fetched again when the route reloads. The new articles list repeats the same pattern in plugins/example/src/pages/articles-page.tsx; both should use the TanStack plugin-route authoring API that exposes queryClient and query options keyed by locale and search.

AGENTS.md reference: AGENTS.md:L52-L52

Useful? React with 👍 / 👎.


type BlogPostsPageData = Awaited<ReturnType<typeof loadPosts>>;

export const route = definePluginRoute<BlogPostsPageData, ContentListSearch>({

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 Declare breadcrumbs for the new list pages

This new page route does not declare route.breadcrumb, so MainBreadcrumb has no current-page crumb for /blog; the new /articles page has the same omission. Add breadcrumb declarations to both route modules, using their declared message namespaces for translated labels.

AGENTS.md reference: AGENTS.md:L30-L30

Useful? React with 👍 / 👎.

Comment on lines +334 to +336
const filters = names.map(name => {
const target = resolveSeoTarget(fields, name);
if (!publicApi.filterableFields.includes(name) || target === null) {

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 Reject pagination keys from list filters

When a content type exposes a filterable field named page, this check accepts it because page is currently absent from RESERVED_FILTER_KEYS. contentListSearch then stores the field value in its reserved pagination property and contentListQuery emits it as the API's page parameter; for example, a boolean filter generates ?page=true, which zodPaginationQuery rejects with a 400 and the page surfaces as a loader error. Reserve page globally or reject it from delivery.list.filters before building the resolved filter.

Useful? React with 👍 / 👎.

@aXenDeveloper
aXenDeveloper deleted the claude/content-list-pages branch October 11, 2026 14:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💡 Feature A new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants