Skip to content

Let resource routes skip the root content middleware - #265

Open
sneridagh wants to merge 2 commits into
mainfrom
feat/resource-routes-skip-content
Open

sneridagh wants to merge 2 commits into
mainfrom
feat/resource-routes-skip-content

Conversation

@sneridagh

@sneridagh sneridagh commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

Problem

The root middleware (fetchPloneContent) runs for every request, including fetcher requests to resource routes. For each one, it loads the content at params['*'] with four expansions, the site and the user, before the route's own loader runs:

  • Performance: each object browser navigation made 5 backend requests, 3 of them unused. /layers.css, requested on every page load, made 3.
  • Error handling: a path that doesn't exist failed with a 404 in the middleware, so a resource route couldn't turn it into a graceful response.

Changes

  • skipContent route option (@plone/types): a registry route entry with skipContent: true doesn't get the content, site and user loaded. Children inherit it; a child can set it back to false.
  • Middleware (apps/aurora): React Router 8 middleware gets only the matched pattern, not the matches or their handle, and the app runs on react-router-serve (no getLoadContext). So the middleware builds the patterns of the flagged routes from config.routes, cached per array, and compares the request's pattern with them. No route names are hardcoded. linkMiddleware skips them too, since it reads the content.
  • App routes: ok and reset-fetcher move to app/app-routes.ts, shared by routes.ts and the middleware, and opt out.
  • Routes that opt out: none of them read the content, site or user contexts.
    • @plone/cmsui: @search, @breadcrumbs, @objectBrowserWidget, @queryStringOptions, @createContent.
    • @plone/contents: the @@contents action routes (@@delete, @@upload, @@rename, @@workflow, @@tags, @@properties, @@order, @@paste).
    • @plone/publicui: layers.css.
  • @objectBrowserWidget loader: runs search and breadcrumbs in parallel. For a folder that doesn't exist (a 4xx), it gives an empty listing instead of an error. 5xx errors are still thrown.
  • @breadcrumbs loader: re-throws the backend status, so a missing path is still a 404, not a 500.
  • Docs: skipContent is documented in the routes how-to (docs/how-to-guides/routes.md), with the updated ReactRouterRouteEntry type.

Backend requests per call

Measured by logging Plone client calls on a dev server:

Request Before After
@objectBrowserWidget navigation 5 2
@search, @queryStringOptions, @breadcrumbs 4 1
/layers.css, /reset-fetcher, /ok 3 0

Behavior to note

  • Expired token: on opted-out routes, the middleware no longer falls back to anonymous access and clears the cookie. The next page load still does.
  • /ok: it no longer calls the backend, so as a health check it only reports that the frontend is up.
  • Give widgets a context instead of the route #261 makes the @objectBrowserWidget loader read breadcrumbs from ploneContentContext. That doesn't work once the route skips the content, so when the two meet, objectBrowserWidget.tsx should keep this PR's version.

Tests

  • Unit: middleware.server.test.ts covers pattern building, inheritance and override, the fetchPloneContent skip, and linkMiddleware without content. The two skip tests fail with the opt-out disabled.
  • Acceptance: new packages/cmsui/acceptance/tests/object-browser.test.ts navigates into a folder in the object browser, and requests the folder the Image add form browses, which doesn't exist. The second test fails on main (404).
  • Suites: aurora, cmsui, contents and react-router vitest pass; lint and check:ts are clean. cmsui + contents acceptance: 72 passed, and the 2 failures passed on rerun (flaky, unrelated).

Found, not fixed here

  • config.routes grows by 8 entries on every request, because installServer() runs per request.
  • @querystringSearch is never registered, so the Querystring widget's preview gets a 404.

The root middleware loaded the content (with four expansions), the site
and the user for every request, including fetcher requests to resource
routes that only call the Plone client. Each object browser navigation
made five backend requests instead of two, and a path that doesn't exist
failed with a 404 before the route's loader could handle it.

Route entries take a new skipContent option. React Router middleware
only gets the matched route pattern, not the matches or their handle, so
the middleware builds the patterns of the flagged routes from the
registry routes (and the app's own ok and reset-fetcher routes, now in
app-routes.ts) and compares the request pattern with them.

The cmsui resource routes, the contents action routes and layers.css
opt out. The object browser loader runs search and breadcrumbs in
parallel and gives an empty listing for a folder that doesn't exist.
@breadcrumbs keeps returning the backend status.
@sneridagh
sneridagh requested a review from pnicolli October 9, 2026 22:15
@sneridagh

Copy link
Copy Markdown
Member Author

@pnicolli it caught this, totally unadverted in front of us... What do you think about the solution?

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant