Skip to content

fix(website-builder): invalidate redirect cache on type change, clarify the choice in Admin - #5505

Open
adrians5j wants to merge 7 commits into
release/6.5.0from
claude/wb-redirect-type-ux
Open

fix(website-builder): invalidate redirect cache on type change, clarify the choice in Admin#5505
adrians5j wants to merge 7 commits into
release/6.5.0from
claude/wb-redirect-type-ux

Conversation

@adrians5j

Copy link
Copy Markdown
Member

Found while investigating why a Permanent → Temporary change appeared to have no effect. Two related problems, both about redirectType.

1. Type changes never invalidated the CDN cache

RedirectAfterUpdateHandler decided whether to flush CloudFront by diffing the old and new record:

// Invalidate cache if any redirect-related field changed or isEnabled changed
if (
    redirect.redirectFrom !== original.redirectFrom ||
    redirect.redirectTo !== original.redirectTo ||
    redirect.isEnabled !== original.isEnabled
) {

redirectType was absent, so the comment was wrong. It is not an incidental field — ActiveRedirectRestMapper.toDto maps it directly to permanent, one of only four values /wb/redirects returns, and the one that decides 308 vs 307.

That response is served with Cache-Control: public, max-age=31536000. The long TTL is deliberate: writes are supposed to flush the CDN via the cloudfrontInvalidateCache task. So changing only the type wrote to the database and then served stale data for up to a year.

What made this particularly hard to spot is that every other field change invalidates within seconds, so the system trains you to expect edits to take effect immediately.

Adds the missing comparison, and rewrites the comment to state the actual invariant — this list must track ActiveRedirectRestMapper.toDto plus isEnabled (which decides whether a redirect appears in the response at all).

2. Admin gave no indication the choice was consequential

"Permanent" produces a 308, which browsers cache indefinitely and keep following even after the redirect is repointed or deleted. There is no way to recall it from a browser that has already seen it. The dialog offered two bare labels with nothing to distinguish them.

Adds a tooltip via .help() (markdown-rendered, so the two options are separate paragraphs):

Temporary — browsers check with the server on each visit, so you can change or remove this redirect later.

Permanent — browsers remember it indefinitely and may keep redirecting even after you change or delete it.

Deliberately no status codes in the copy — "308" tells an editor nothing; the consequence is what they need.

Also changes the default from permanent to temporary. The option you get by not choosing shouldn't be the one you can't undo. Flagging this explicitly as the reviewable decision here: the counter-argument is that Permanent is the SEO-correct choice for a genuine move and probably the common case, so the default nudges users toward the weaker option. I lean toward recoverable-by-default, but it is a product call.

defaultValue only applies when no value is set, and EditRedirectPresenter populates from the stored record, so existing redirects keep their saved type. Only newly created ones are affected.

Follow-up, not in this PR

InvalidateRedirectsCacheUseCase triggers cloudfrontInvalidateCache, a task defined in api-file-manager-s3. Website Builder borrowing File Manager's task is an undeclared cross-package dependency: if that package isn't registered, the task id doesn't exist and every redirect invalidation fails. Silently — InvalidateRedirectsCacheUseCase returns Result.fail, but all three redirect event handlers await it and discard the result. Worth either giving WB its own task or extracting a shared one, and stopping the handlers from swallowing the failure.

Test plan

  • yarn adio, yarn lint, yarn format, yarn webiny sync-dependencies
  • api-website-builder and app-website-builder both build
  • Edit a redirect's type only, confirm a CloudFront invalidation is created and /wb/redirects reflects the new permanent value
  • Confirm the tooltip renders with both paragraphs in the Create and Edit dialogs
  • Confirm a newly created redirect defaults to Temporary and existing ones keep their stored type

Manual — RedirectAfterUpdateHandler has no automated coverage.

🤖 Generated with Claude Code

adrians5j and others added 7 commits July 28, 2026 17:16
…fy the choice in Admin

Two related redirect-type problems.

The CDN cache was never invalidated when a redirect's type changed.
`RedirectAfterUpdateHandler` diffed `redirectFrom`, `redirectTo` and
`isEnabled`, but not `redirectType` — despite its own comment claiming it
covered "any redirect-related field". `redirectType` maps straight to the
`permanent` flag in the `/wb/redirects` response, which is served with
`max-age=31536000`, so switching Permanent <-> Temporary saved to the
database and then served stale data for up to a year. Every other field
change flushes within seconds, which made this especially misleading.

The Admin dialog also gave no indication that the choice matters.
"Permanent" produces a 308, which browsers cache indefinitely and keep
following even after the redirect is changed or deleted — not something a
dropdown with two bare labels conveys. Adds a tooltip explaining the
consequence of each option in plain terms, without leaning on status
codes, and makes Temporary the default so the unrecoverable option is
opted into rather than inherited.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… reference

Sven's feedback on the tooltip: the permanent/temporary choice is a
redirect status code, and the copy never said so. Names both pairs —
Temporary (302/307) and Permanent (301/308) — rather than only the
308/307 the Next.js starter kit happens to send, since the platform
itself emits no status code (the API exposes `permanent: boolean` and
each frontend picks the code).

Adds the MDN redirections guide as an inline link. It goes in
`description`, not in the tooltip: the tooltip is a Radix `Tooltip`,
whose content is not meant to hold interactive elements — a link there
is unreliable to click and unreachable by keyboard.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Keeps the reference with the explanation it belongs to, rather than
adding a permanently visible line to the dialog. `TooltipContent` sets no
`pointer-events: none`, and Radix keeps the tooltip open while the
pointer is over its content, so the link is reachable by mouse.

Worth knowing it is still not reachable by keyboard — Radix tooltips are
not built to hold interactive content, and the trigger is an icon rather
than a focusable control. If that matters, the link belongs back in
`description`, which renders inline and is focusable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…redirect type tooltip

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…omment

`yarn format:check` was failing on release/6.5.0 for
skills/user-skills/admin/ui-extensions/SKILL.md, which blocked CI on every
PR targeting that branch. Reformats it.

Also rescopes the comment on the Frontend Integration API key's
permissions. Merging release/6.5.0 brought in the rename and the new
CMS/languages entries, which left the Website Builder note sitting above
the whole array while describing only its last two entries. Moved down to
the entries it actually explains.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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