Skip to content

Add element-level opt-out for page caching - #3980

Open
robinboening wants to merge 2 commits into
AlchemyCMS:mainfrom
robinboening:feature_element_level_page_cache_optout
Open

Add element-level opt-out for page caching#3980
robinboening wants to merge 2 commits into
AlchemyCMS:mainfrom
robinboening:feature_element_level_page_cache_optout

Conversation

@robinboening

@robinboening robinboening commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Closes #3973.

This adds a page_cache option to element definitions, allowing individual elements to opt a page out of HTTP page caching:

- name: restricted_downloads
  page_cache: false

When a page contains an element definition with page_cache: false, Alchemy now always renders the page instead of relying on the page-cache. This lets dynamic or permission-sensitive elements execute and apply their own fragment cache keys.

The opt-out uses Cache-Control: no-store rather than no-cache, because no-cache may still allow stored responses to be revalidated and return 304, which would skip rendering the element entirely.

The option defaults to true and is kept as definition-only metadata, so existing element definitions and page cache behavior remain unchanged unless an element explicitly opts out.

Checklist

  • I have followed Pull Request guidelines
  • I have added a detailed description into each commit message
  • I have added tests to cover this change

@robinboening
robinboening requested a review from a team as a code owner June 17, 2026 16:51
@codecov

codecov Bot commented Jun 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.27%. Comparing base (4ccf434) to head (555daa2).
⚠️ Report is 40 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3980      +/-   ##
==========================================
+ Coverage   98.26%   98.27%   +0.01%     
==========================================
  Files         350      351       +1     
  Lines        9184     9241      +57     
==========================================
+ Hits         9025     9082      +57     
  Misses        159      159              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@robinboening
robinboening force-pushed the feature_element_level_page_cache_optout branch 2 times, most recently from 19a8a0b to e5c19c5 Compare June 17, 2026 21:03
tvdeyen
tvdeyen previously approved these changes Jul 29, 2026

@tvdeyen tvdeyen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I like that feature. Would you mind to fix the remaining specs? We plan to make significant changes to the repo and this will break this PR. Also we plan to release 8.4 soon and we would include in that release

Allow element definitions to declare page_cache: false so pages that contain or can render those elements skip HTTP page-shell caching.

Use no-store for this opt-out because no-cache still permits stored responses and conditional revalidation, which can return 304 before element-level cache variants are rendered.

Keep the option as definition-only metadata and cover the new behavior with model and request specs.
@tvdeyen
tvdeyen force-pushed the feature_element_level_page_cache_optout branch from e5c19c5 to 8572837 Compare July 29, 2026 08:03
@robinboening

Copy link
Copy Markdown
Contributor Author

I like that feature. Would you mind to fix the remaining specs? We plan to make significant changes to the repo and this will break this PR. Also we plan to release 8.4 soon and we would include in that release

Fixed. The expectation was wrong. The dynamically created uncached_layout does not specify cache: 60, so Page#expiration_time correctly falls back to the configured default of 600 seconds.

@tvdeyen tvdeyen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice feature — the no-store reasoning is spot on. 👍 One thing to flag: page_cache_disabled_by_elements? runs on every show/index, and because it calls @page.find_elements, it now loads and instantiates the full published element set on every request — including conditional GETs that return 304. Previously a 304 only ran the lightweight EtagGenerator pluck and never materialized elements, so this adds real work to the hot cache path. It's also evaluated up to 3× per request (twice in set_expiration_headers, once via render_fresh_page?) with no memoization.

Two small things make it nearly free again:

  1. Short-circuit statically — in the common case no definition opts out, so we can skip the DB entirely; and when one does, an exists? is far cheaper than loading full records (it also naturally covers nested/fixed elements, which find_elements' default visible.not_nested.unfixed scope currently misses).
  2. Memoize per request (guarding with defined? since the result can be false).
def page_cache_disabled_by_elements?
  return @page_cache_disabled_by_elements if defined?(@page_cache_disabled_by_elements)

  opt_out_names = Alchemy::Element.definitions.filter_map { |d| d.name if d.page_cache == false }
  @page_cache_disabled_by_elements = !!(
    opt_out_names.any? &&
      @page&.public_version&.elements&.published&.exists?(name: opt_out_names)
  )
end

This keeps behavior identical for the specs here, stays zero-cost for apps that don't use the opt-out, and closes the nested/fixed-element gap for free. 🙂

@tvdeyen
tvdeyen dismissed their stale review August 5, 2026 12:46

performance regression

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.

Add element-level opt-out for page caching

2 participants