Performance & native build-config compatibility¶
MageObsidian serves every stylesheet and script through Vite, not Magento's legacy Less/RequireJS pipelines. That changes how the native build-configuration flags behave, and it opens one optional optimization worth understanding: the critical CSS path.
This page covers both: the opt-in critical-CSS feature, and how Magento's dev/js/*, dev/css/* and dev/template/minify_html flags interact with an Obsidian theme.
Critical CSS (optional)¶
On an Obsidian theme the only render-blocking resource left in the head is the Vite stylesheet (style.css). The critical-CSS path inlines the above-the-fold CSS into the <head> and defers that stylesheet, so the first paint no longer waits for it.
It is off by default and opt-in.
Reuses the native flag¶
There is no MageObsidian-specific config. The feature reuses Magento's own switch, dev/css/use_css_critical_path (off by default). When you turn it on, MageObsidian inlines the critical CSS for the current layout handle and defers the stylesheet.
The defer is per-page, not global¶
Magento's native critical-path switch also activates AsyncCssPlugin, which rewrites every <link rel="stylesheet"> in the response to an async (media="print") load β globally. On any page that has no inlined critical CSS that produces a flash of unstyled content (FOUC).
MageObsidian avoids this:
- It neutralizes
AsyncCssPluginfor Obsidian themes only (legacy and admin themes keep the native behaviour). - It defers the stylesheet per page, in the head template, coupled to whether critical CSS exists for the active handle. A page with no critical CSS keeps the stylesheet render-blocking β never deferred without an inline counterpart, so there is never a FOUC.
Generating the critical CSS¶
The critical CSS is extracted from the real rendered page (so it reflects the actual above-the-fold markup) with Beasties, by a console command:
The command renders the URL, extracts the critical subset, rewrites the @font-face url(...) to an absolute (root-relative) static URL so the self-hosted fonts resolve once inlined in <head>, and writes web/generated/critical/<handle>.css in the active theme. Run it after the theme build and setup:static-content:deploy, because it reads the built stylesheet and resolves the deployed static URLs.
| Option | Default | Purpose |
|---|---|---|
--handle |
cms_index_index |
Layout handle the critical CSS is for. |
--url |
store secure base URL | The page to render. |
--store |
default store view | Store to emulate. |
--insecure |
off | Skip TLS verification (self-signed dev certs). |
--resolve |
β | A curl host:port:ip entry, for local setups behind a custom host. |
--node / --bin |
node / shipped bin |
Override the Node binary or the extractor script. |
Which pages to cover β it depends on your traffic¶
Critical CSS is generated per layout handle β one file per handle, and only pages whose handle has a generated file get the inline treatment. It is not a global switch; think of it as a per-entry-page optimization:
- The benefit concentrates on the first page of a visit, with a cold cache. From the second page on,
style.cssis already in the browser cache and the render-blocking request costs close to nothing. - If visitors mostly enter through the home page, the default
cms_index_indexcovers most of the win. - If organic search or ads land visitors directly on products and categories, those are first pages too β generate their handles as well:
One granularity caveat: a handle's critical CSS is extracted from one representative page and applied to every page sharing that handle (all product pages share catalog_product_view). The above-the-fold structure is normally identical across them; anything a specific page needs beyond the extract simply arrives with the deferred stylesheet β correctness is guaranteed, the optimization is partial.
Any handle without a generated file degrades to the render-blocking stylesheet.
Regenerate on every deploy¶
web/generated/critical/ is build output, not shipped source: a composer update of the theme β or any deploy that replaces it β wipes the generated files. Bake the generation into your deploy pipeline as its last step, once the site is already serving the new release (caches flushed, PHP-FPM restarted):
Generating earlier extracts the CSS from the previous release β or fails outright against a half-deployed site. The final cache:clean full_page matters because pages cached before the file existed were rendered with the fallback stub.
Read-only infrastructure (Adobe Commerce Cloud)¶
The generated file is written into the active theme's directory, which is read-only at runtime on Adobe Commerce Cloud and similar immutable deployments β while generation needs the live site, which only exists after the build phase. On those platforms, leave dev/css/use_css_critical_path off for now: pages stay correct with the render-blocking stylesheet. A runtime-writable storage location (so generation can run in a post_deploy hook) is under consideration.
Enabling it¶
The flag is scoped per store view. If the generated file is missing or unreadable, the head falls back to a tiny [v-cloak] stub and the normal render-blocking stylesheet β the page is always correct.
When not to use it¶
Critical CSS is a trade-off: it inlines CSS into every cached page (more HTML) to remove one render-blocking request. If a project does not want it, leave dev/css/use_css_critical_path off β the stylesheet stays render-blocking, which behind Varnish with gzip/brotli and HTTP/2 already loads in tens of milliseconds. Nothing else needs to change.
Native build-config flags¶
JS/CSS merge & minify β inert¶
dev/js/merge_files, dev/js/minify_files, dev/css/merge_css_files, dev/css/minify_files have no effect on an Obsidian theme. Those flags act on Magento's AssetCollection; MageObsidian emits its CSS and JS straight from Vite output (the importmap, the <link>, the <script type="module">), which never enters that collection. You can leave them at any value β minification and bundling are already handled by the Vite build.
HTML minification β disabled per theme¶
dev/template/minify_html is turned off for Obsidian themes (admin and legacy themes still minify). Two reasons:
- In production, with neither
force_html_minificationnor SCD-on-demand set, Magento returns the path to a pre-generated.mintemplate without creating it; that file never exists for the Vite/Twig pipeline, so the include fails with HTTP 500. - The regex minifier can corrupt the inline JSON the runtime emits β island
data-props, the importmap, JSON-LD β which is risky for no real gain (Varnish + gzip/brotli already collapse whitespace; the post-compression delta is ~1β3%).
You can leave the flag on globally; the storefront is unaffected.
Varnish and the back/forward cache¶
Magento's generated VCL ends every non-static response with
whatever the application sent. no-store is the one directive Chrome reads as
"you may not keep this page", so behind Varnish the back/forward cache is off
for the whole storefront: every Back is a fresh document instead of the page
the shopper left, scroll position and all.
Nothing is broken without it. An Obsidian storefront renders its islands from markup the server already sent, so a page arrives finished whether it comes from the back/forward cache, from Varnish or from PHP β the end-to-end suite asserts exactly that, and it passes with the stock VCL. What the header costs is the instant Back, not a correct one.
Turning it back on is a change to the VCL, which belongs to whoever runs the edge, not to this package. Two lines:
obj.uncacheable is already read a few lines above in Magento's own template, so
this adds no new concept. A page Varnish cached is by definition the same for
every visitor, so it holds nothing that no-store protects; the cart, the
checkout, the account and customer/section/load stay no-store because they
are uncacheable and take the first branch. The browser still revalidates before
reusing anything β no-cache forbids serving it without asking.
Regenerate and reload after changing it:
The /checkout bypass¶
The same template refuses to cache anything under /checkout:
MageObsidian's checkout is a cacheable shell with its private half loaded
from customer-data, so this line silently throws that away β the page is rebuilt
by PHP on every view. Dropping || req.url ~ "/checkout" restores it. The cart
and the success page are not cached either way: they answer with no-store
themselves and Varnish passes them on that basis, which is the check that
actually protects a shopper.