Skip to content

fix(ui): server-render the dashboard activity widget - #18721

Merged
GermanJablo merged 9 commits into
mainfrom
fix/recents-widget-ssr
Oct 20, 2026
Merged

GermanJablo merged 9 commits into
mainfrom
fix/recents-widget-ssr

Conversation

@GermanJablo

@GermanJablo GermanJablo commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Drafted with AI

The activity widget (Pinned and Recently viewed) now renders on the server, so it shows up with the rest of the dashboard instead of after a client fetch. No loading state on page load and no layout shift.

Why?

It was the only built-in widget that rendered nothing until a ResizeObserver measured the grid and then fetched one page of documents.

How?

  • The server loads the first 8 documents for both tabs, so switching tabs is instant.
  • CSS container queries now own the layout: 2×2, then 3×2, then one row of 4 to 8 columns. Each tier sets --recents-page-size and hides the extra cards with nth-child, so the server HTML already looks like the hydrated widget.
  • The single remaining ResizeObserver only reads --recents-page-size for pagination. Before hydration, CSS picks the right pagination variant.
  • The client fetches in batches of 8 and only what's missing for the current page.

Numbers

Prod build, 1600px, median of 20 runs:

main This PR
TTFB 15ms 21ms
Cards visible 295ms 54ms
LCP 320ms 96ms
Client fetches on load 1 0

Thumbnails add under 1ms on the server, so they stay server-rendered, and the widget doesn't need a Suspense boundary.

Preview

payload-pr-demo-recents-widget-take-1.mp4

No pinned documents: reloads and resizes keep a single empty card, with no layout shift and no client fetches.

payload-pr-demo-recents-no-pins-take-1.mp4

Pagination: the first server-rendered batch pages without fetching, the next batches load on demand, and the current page's first card stays visible across width changes.

payload-pr-demo-recents-pagination-take-1.mp4

@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

📦 esbuild Bundle Analysis for payload

This analysis was generated by esbuild-bundle-analyzer. 🤖

Meta File Out File Size (raw) Note
packages/next/meta_index.json esbuild/index.js 279.11 KB 🆕 Added
packages/payload/meta_index.json esbuild/index.js 1.93 MB 🆕 Added
packages/payload/meta_shared.json esbuild/exports/shared.js 543.06 KB 🆕 Added
packages/richtext-lexical/meta_client.json esbuild/exports/client_optimized/index.js 297.02 KB 🆕 Added
packages/ui/meta_client.json esbuild/exports/client_optimized/index.js 1.32 MB 🆕 Added
packages/ui/meta_shared.json esbuild/exports/shared_optimized/index.js 19.46 KB 🆕 Added
Largest paths These visualization shows top 20 largest paths in the bundle.

Meta file: packages/next/meta_index.json, Out file: esbuild/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ████████████████████████▋ }}}$ 98.8%, 272.71 KB
dist/adapters/router.js ${{\color{Goldenrod}{ }}}$ 0.3%, 718 B
dist/adapters/server.js ${{\color{Goldenrod}{ }}}$ 0.2%, 533 B
dist/adapters/layout.js ${{\color{Goldenrod}{ }}}$ 0.2%, 529 B
dist/adapters/views.js ${{\color{Goldenrod}{ }}}$ 0.2%, 469 B
dist/adapters/UnmountWhenHidden ${{\color{Goldenrod}{ }}}$ 0.2%, 449 B
dist/utilities/initAdminContext.js ${{\color{Goldenrod}{ }}}$ 0.1%, 366 B
dist/utilities/selectiveCache.js ${{\color{Goldenrod}{ }}}$ 0.1%, 263 B
dist/esbuildEntry.js ${{\color{Goldenrod}{ }}}$ 0.0%, 0 B

Meta file: packages/payload/meta_index.json, Out file: esbuild/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ █████████████████▏ }}}$ 68.5%, 1.32 MB
dist/collections/operations ${{\color{Goldenrod}{ ▉ }}}$ 3.6%, 68.23 KB
dist/fields/hooks ${{\color{Goldenrod}{ ▋ }}}$ 2.5%, 48.00 KB
dist/uploads/fileVersioning ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 22.30 KB
dist/globals/operations ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 19.50 KB
dist/auth/operations ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 17.10 KB
dist/utilities/configToJSONSchema.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 16.04 KB
dist/queues/operations ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 14.32 KB
dist/fields/config ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 13.77 KB
dist/collections/config ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 11.96 KB
dist/utilities/telemetry ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 11.93 KB
dist/fields/validations.js ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 10.29 KB
dist/cli/commands ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 9.93 KB
dist/config/orderable ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 8.78 KB
dist/uploads/fetchAPI-multipart ${{\color{Goldenrod}{ }}}$ 0.4%, 8.39 KB
dist/config/sanitize.js ${{\color{Goldenrod}{ }}}$ 0.4%, 8.16 KB
dist/database/migrations ${{\color{Goldenrod}{ }}}$ 0.4%, 8.16 KB
dist/utilities/entityInputSchema ${{\color{Goldenrod}{ }}}$ 0.4%, 8.02 KB
dist/uploads/generateFileData.js ${{\color{Goldenrod}{ }}}$ 0.4%, 7.97 KB
dist/collections/endpoints ${{\color{Goldenrod}{ }}}$ 0.4%, 7.85 KB
(other) ${{\color{Goldenrod}{ ███████▉ }}}$ 31.5%, 604.56 KB

Meta file: packages/payload/meta_shared.json, Out file: esbuild/exports/shared.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ██████████████████████ }}}$ 88.2%, 474.44 KB
dist/fields/validations.js ${{\color{Goldenrod}{ ▍ }}}$ 1.9%, 10.26 KB
dist/fields/config ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 5.83 KB
dist/utilities/traverseFields.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 4.34 KB
dist/utilities/deepCopyObject.js ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 3.52 KB
dist/collections/config ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 3.44 KB
dist/config/orderable ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 3.13 KB
dist/fields/baseFields ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 2.79 KB
dist/config/client.js ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 2.62 KB
dist/auth/cookies.js ${{\color{Goldenrod}{ }}}$ 0.3%, 1.55 KB
dist/utilities/flattenTopLevelFields.js ${{\color{Goldenrod}{ }}}$ 0.3%, 1.42 KB
dist/utilities/getVersionsConfig.js ${{\color{Goldenrod}{ }}}$ 0.2%, 1.04 KB
dist/globals/config ${{\color{Goldenrod}{ }}}$ 0.2%, 957 B
dist/uploads/generatePayloadFileURL.js ${{\color{Goldenrod}{ }}}$ 0.2%, 864 B
dist/utilities/unflatten.js ${{\color{Goldenrod}{ }}}$ 0.2%, 850 B
dist/utilities/flattenAllFields.js ${{\color{Goldenrod}{ }}}$ 0.1%, 798 B
dist/errors/ValidationError.js ${{\color{Goldenrod}{ }}}$ 0.1%, 772 B
dist/utilities/sanitizeUserDataForEmail.js ${{\color{Goldenrod}{ }}}$ 0.1%, 713 B
dist/auth/extractJWT.js ${{\color{Goldenrod}{ }}}$ 0.1%, 696 B
dist/utilities/getFieldPermissions.js ${{\color{Goldenrod}{ }}}$ 0.1%, 681 B
(other) ${{\color{Goldenrod}{ ██▉ }}}$ 11.8%, 63.68 KB

Meta file: packages/richtext-lexical/meta_client.json, Out file: esbuild/exports/client_optimized/index.js

Path Size
dist/features/blocks ${{\color{Goldenrod}{ ███▌ }}}$ 14.5%, 42.69 KB
dist/lexical/plugins ${{\color{Goldenrod}{ ██▉ }}}$ 11.7%, 34.51 KB
dist/lexical/ui ${{\color{Goldenrod}{ ██▉ }}}$ 11.5%, 33.66 KB
dist/features/table ${{\color{Goldenrod}{ ██▎ }}}$ 9.3%, 27.46 KB
dist/features/toolbars ${{\color{Goldenrod}{ █▋ }}}$ 6.8%, 19.93 KB
dist/features/link ${{\color{Goldenrod}{ █▌ }}}$ 6.3%, 18.56 KB
dist/features/upload ${{\color{Goldenrod}{ █▏ }}}$ 4.8%, 14.03 KB
dist/features/textState ${{\color{Goldenrod}{ ▉ }}}$ 3.8%, 11.08 KB
dist/lexical/utils ${{\color{Goldenrod}{ ▊ }}}$ 3.4%, 10.01 KB
dist/features/relationship ${{\color{Goldenrod}{ ▊ }}}$ 3.2%, 9.32 KB
dist/features/converters ${{\color{Goldenrod}{ ▋ }}}$ 2.9%, 8.41 KB
dist/utilities/fieldsDrawer ${{\color{Goldenrod}{ ▋ }}}$ 2.7%, 8.00 KB
dist/features/debug ${{\color{Goldenrod}{ ▋ }}}$ 2.5%, 7.40 KB
dist/lexical/config ${{\color{Goldenrod}{ ▍ }}}$ 1.7%, 5.14 KB
dist/features/indent ${{\color{Goldenrod}{ ▎ }}}$ 1.4%, 4.19 KB
dist/features/lists ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 3.67 KB
dist/lexical/LexicalEditor.js ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 3.33 KB
dist/features/horizontalRule ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 3.29 KB
dist/features/format ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 3.28 KB
dist/field/Field.js ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 3.06 KB
(other) ${{\color{Goldenrod}{ █████████████████████▍ }}}$ 85.5%, 251.12 KB

Meta file: packages/ui/meta_client.json, Out file: esbuild/exports/client_optimized/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ███████▎ }}}$ 29.2%, 382.60 KB
dist/elements/Hierarchy ${{\color{Goldenrod}{ ▉ }}}$ 3.8%, 49.35 KB
dist/elements/BulkUpload ${{\color{Goldenrod}{ ▌ }}}$ 2.3%, 30.27 KB
dist/elements/Table ${{\color{Goldenrod}{ ▌ }}}$ 2.2%, 28.90 KB
dist/views/Version ${{\color{Goldenrod}{ ▌ }}}$ 2.1%, 27.71 KB
dist/views/Edit ${{\color{Goldenrod}{ ▍ }}}$ 1.7%, 22.21 KB
dist/views/HierarchyList ${{\color{Goldenrod}{ ▍ }}}$ 1.6%, 21.55 KB
dist/views/Dashboard ${{\color{Goldenrod}{ ▍ }}}$ 1.5%, 19.82 KB
dist/elements/WhereBuilder ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 17.65 KB
dist/providers/Auth ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 17.41 KB
dist/forms/Form ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 16.50 KB
dist/fields/Relationship ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 16.23 KB
dist/widgets/RecentlyViewed ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 15.22 KB
dist/fields/Upload ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 14.96 KB
dist/fields/Blocks ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 14.91 KB
dist/elements/Popup ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 13.32 KB
dist/views/List ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 13.21 KB
dist/elements/ReactSelect ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 12.98 KB
dist/elements/PublishButton ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 12.00 KB
dist/elements/QueryPresets ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 10.82 KB
(other) ${{\color{Goldenrod}{ █████████████████▋ }}}$ 70.8%, 929.63 KB

Meta file: packages/ui/meta_shared.json, Out file: esbuild/exports/shared_optimized/index.js

Path Size
dist/graphics/Logo ${{\color{Goldenrod}{ ███████▍ }}}$ 29.9%, 5.61 KB
../../node_modules ${{\color{Goldenrod}{ ███▌ }}}$ 14.1%, 2.65 KB
dist/graphics/Icon ${{\color{Goldenrod}{ ██ }}}$ 8.1%, 1.51 KB
dist/utilities/formatDocTitle ${{\color{Goldenrod}{ █▊ }}}$ 7.0%, 1.32 KB
dist/providers/TableColumns ${{\color{Goldenrod}{ █▏ }}}$ 4.6%, 866 B
dist/utilities/getGlobalData.js ${{\color{Goldenrod}{ █ }}}$ 4.1%, 762 B
dist/utilities/api.js ${{\color{Goldenrod}{ █ }}}$ 4.0%, 756 B
dist/utilities/groupNavItems.js ${{\color{Goldenrod}{ █ }}}$ 4.0%, 745 B
dist/utilities/traverseForLocalizedFields.js ${{\color{Goldenrod}{ ▊ }}}$ 3.0%, 570 B
dist/elements/Translation ${{\color{Goldenrod}{ ▋ }}}$ 2.6%, 493 B
dist/forms/Form ${{\color{Goldenrod}{ ▋ }}}$ 2.5%, 474 B
dist/utilities/handleTakeOver.js ${{\color{Goldenrod}{ ▌ }}}$ 2.3%, 440 B
dist/elements/withMergedProps ${{\color{Goldenrod}{ ▍ }}}$ 1.8%, 339 B
dist/utilities/getNavGroups.js ${{\color{Goldenrod}{ ▍ }}}$ 1.8%, 338 B
dist/utilities/getVisibleEntities.js ${{\color{Goldenrod}{ ▍ }}}$ 1.8%, 329 B
dist/elements/WithServerSideProps ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 233 B
dist/layouts/Root ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 230 B
dist/utilities/handleGoBack.js ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 180 B
dist/fields/mergeFieldStyles.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 158 B
dist/utilities/handleBackToDashboard.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 152 B
(other) ${{\color{Goldenrod}{ █████████████████▌ }}}$ 70.1%, 13.16 KB
Details

Next to the size is how much the size has increased or decreased compared with the base branch of this PR.

  • ‼️: Size increased by 20% or more. Special attention should be given to this.
  • ⚠️: Size increased in acceptable range (lower than 20%).
  • ✅: No change or even downsized.
  • 🗑️: The out file is deleted: not found in base branch.
  • 🆕: The out file is newly found: will be added to base branch.

@coderabbitai

This comment was marked as spam.

coderabbitai[bot]

This comment was marked as low quality.

@GermanJablo
GermanJablo added this pull request to stack #18792 October 16, 2026 19:58
@GermanJablo
GermanJablo force-pushed the fix/recents-widget-ssr branch from a35a3cd to bc2ffa4 Compare October 16, 2026 20:09
@GermanJablo
GermanJablo removed this pull request from stack #18792 October 16, 2026 20:46
@GermanJablo
GermanJablo added this pull request to stack #18800 October 16, 2026 20:46
@GermanJablo
GermanJablo force-pushed the fix/recents-widget-ssr branch from bc2ffa4 to 1a3364d Compare October 16, 2026 20:46
coderabbitai[bot]

This comment was marked as resolved.

@GermanJablo
GermanJablo force-pushed the fix/recents-widget-ssr branch from 1a3364d to e974d97 Compare October 19, 2026 17:56
@GermanJablo
GermanJablo force-pushed the fix/recents-widget-ssr branch from e974d97 to 80914d5 Compare October 19, 2026 21:00

@JarrodMFlesch JarrodMFlesch left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good, pretty identical to the changes I proposed in #18735, so I am aligned.

@GermanJablo
GermanJablo merged commit 9c39e43 into main Oct 20, 2026
571 of 573 checks passed
@GermanJablo
GermanJablo deleted the fix/recents-widget-ssr branch October 20, 2026 13:39
GermanJablo added a commit that referenced this pull request Oct 20, 2026
> Drafted with AI

Stacked on #18721. Marks the built-in dashboard widget components as
`@internal`, using the same JSDoc tag as `useControllableState`.

### Why?

They're exported so Payload's default dashboard config can reference
them through the import map, for example
`@payloadcms/ui/rsc#CollectionCards`. Users configure built-in widgets
by slug (`welcome`, `activity`, `collections`), as the dashboard docs
describe, so they have no reason to import these components directly.

Marking them `@internal` lets us rename or restructure built-in widgets
without treating it as a breaking change, starting with #18790.

### What changes

JSDoc only. No exports are removed and nothing is `stripInternal`-ed
from the published types.

- `@payloadcms/ui/rsc`: `CollectionCards`, `CollectionQueryWidget`,
`RecentlyViewedWidget`, `UploadDropzoneWidget`, `WelcomeWidget`
- `@payloadcms/ui`: `CollectionCardsClient`, `CollectionQuerySortField`,
`RecentlyViewedCollectionsField`, `RecentsAndPinnedClient`,
`UploadDropzoneCollectionsField`, `UploadDropzoneWidgetClient`

Co-authored-by: German Jablonski <GermanJablo@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants