Currently Available: Need a skilled Software Developer for your next project?
Categories
Shopify

How Shopify CLI Renders Theme Code That Isn’t Uploaded Yet

Every page you see in shopify theme dev is rendered on Shopify's servers, including the file you saved half a second ago that the CLI hasn't uploaded yet. The CLI sends your local file contents to the storefront in a form field called replace_templates, which I couldn't find anywhere in Shopify's docs, and Shopify renders the page with them.

theme dev gives you http://127.0.0.1:9292 with hot reload and real store data. Liquid can't run on your machine, though. The renderer is Shopify's own server software, and the products, prices and cart live in the store. So I wanted to know what the local server is doing. Shopify CLI is open source, and the answer is in a handful of files under packages/theme/src/cli/utilities/theme-environment/.

When theme dev starts, it uploads your theme into a hidden development theme. Then it requests the store's homepage with ?preview_theme_id=<id>&_fd=0&pb=0 and keeps the _shopify_essential cookie that comes back. Requests with that cookie render the development theme. This is the same preview mechanism you can use in any browser. Every render request the CLI sends also carries your storefront token as a Bearer header.

After that, every file you save is uploaded in the background. Until its upload finishes, the file sits in a set called unsyncedFileKeys. If the page is requested in that window, the CLI doesn't send a plain GET. In storefront-renderer.ts it sends a POST to the same storefront URL, and the body carries the changed files. Simplified, it looks like this:

POST https://your-store.myshopify.com/products/linen-shirt?_fd=0&pb=0
Authorization: Bearer <storefront token>
Content-Type: application/x-www-form-urlencoded

replace_templates[sections/main-product.liquid]=<your local file>
_method=GET

Shopify handles it as a GET for that page and uses your version of sections/main-product.liquid instead of the stored one. When the upload finishes, the file leaves the set and the next request is a normal GET again. Only Liquid and JSON files outside assets/ go through this. For JSON templates the CLI sends only the template of the current route, and for locales only the files of the current language. CSS and JavaScript files are not sent to the renderer. The local server answers requests for them from your disk and marks the responses with an X-Local-Asset: true header.

Hot reload uses the same mechanism for single sections. The browser keeps a text/event-stream connection open to the CLI, and when you save a file, the CLI sends one event right away and a second one after the upload. Shopify's hot reload script, which the store serves at /cdn/shopifycloud/theme-hot-reload/theme-hot-reload.js, reacts to the first event when it runs on localhost. In previews on the myshopify.com domain it waits for the second. On localhost it then asks for just the changed section, and the CLI forwards that request with section_id, which is the public Section Rendering API, plus replace_templates for the section file and for any changed JSON template that references it.

Before adding its hot reload script, the CLI checks whether the HTML already contains <script id="hot-reload-client">. The comment on that check says "Already injected in SFR", and SFR is Shopify's storefront renderer, so in some cases the renderer adds the CLI's script itself. Sessions that use a Theme Access password, with tokens starting with shptka_, send their render requests through a Shopify proxy under /cli/sfr instead of the store's domain.

The CLI docs cover hot reload and development themes, but say nothing about how a page with unsaved changes gets rendered. Shopify owns both the CLI and the renderer, so it can keep a private parameter like this and change it whenever it wants. Another tool could send the same request, but it would depend on an API that has exactly one official client.

You can watch it with shopify theme dev --verbose. Save a section file, and the section render usually shows up in the log as → Rendering <url> (with sections/your-section.liquid).... Once the upload has caught up, the same page shows up as → Rendering <url>... without the file list. In the browser's network panel, your stylesheets and scripts come back with X-Local-Asset: true.

What I'm building

Delegate tasks. Get software.

Give Vroni a GitHub issue, bug report, spec, or rough idea. It reads the repo, plans the change, writes code, runs checks, and works toward a review-ready pull request.

Take a look at vroni.com

Email updates

Usually a new article and a few links I found interesting.

No spam. Unsubscribe with one click.

Leave a Reply

Your email address will not be published. Required fields are marked *