Skip to content

Feature: Allow extending page ETags with dynamic components (e.g. asset digests) #3352

@robinboening

Description

@robinboening

I recently did some maintenance work on an application that uses Alchemy CMS and replaced good old Webpacker with ViteRuby to improve build times. Everything was looking great and builds are now insanely fast. A few days later, I noticed that some pages wouldn’t load the referenced application.css, as the browser received a 404. The underlying reason is that ETags change when page content is updated, but not when frontend assets change. As a result, after rebuilding assets with Vite, browsers may still receive 304 Not Modified and keep using outdated HTML that references old assets.

This issue was not noticeable with Webpacker because it integrated more tightly with Rails asset digest system. The stylesheet_pack_tag helper produced fingerprinted asset URLs that were tracked as template dependencies, so cache digests and ETags automatically changed whenever assets were rebuilt.

I fixed the issue by hashing the Vite entrypoints (in an initializer) and using the resulting digest as an additional component in the ETag generation in the PagesController.

# Alchemy::PagesController
def page_etag
  [@page, Alchemy::Vite.assets_digest, current_alchemy_user]
end

I think it would be great if ViteRuby also hooked into ActionView for a seamless integration. But this might not be within their scope, idk. At the same time, it would be very useful if Alchemy provided a way to hook into its ETag generation, so the main application could supply additional components such as asset digests.

I haven't really looked deeper in to the new Configuration class, but from what I've seen maybe the cache key for the etag can move into Configurations::PageCache and then be used like this, what do you think?

# Alchemy::PagesController
def render_fresh_page?
  must_not_cache? || stale?(
    etag: Alchemy.config.page_cache.cache_key_for(@page, current_alchemy_user),
    last_modified: @page.last_modified_at,
    ...
  )
end
# main app
# config/initializers/alchemy_page_cache.rb
Alchemy::Configurations::PageCache.extra_cache_key_parts << lambda do
  entrypoints = ...
  Digest::SHA1.hexdigest(
    entrypoints.map { |e| ViteRuby.instance.manifest.path_for(e) }.join
  )
end

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions