Andrew Welch
Published , updated · 5 min read · RSS Feed
Please consider 🎗 sponsoring me 🎗 to keep writing articles like this.
Headless Preview in Craft CMS
Craft CMS 3.2 introduced headless content preview. Here’s an exploration of how it works, and how you can implement it
Craft CMS has long had a “live preview” feature that allows content authors to see a preview of exactly what their content will look like when published to the web.
With Craft CMS 3.2, one of the major features added was “headless preview”.
This feature allows developers who are rendering their pages as a Single Page Application (SPA) via a frontend framework like React, Vue.js, Svelte, etc. the ability to have Craft CMS content preview, too.
Link Why it required a rewrite
Even though nowhere in “content management system” is the promise of a rendering engine, all traditional CMSs don’t just manage content, but also render it as web pages as well.
A CMS rendering content was probably born out of convenience.
When you use a CMS “headless” you are lopping off the part that does the rendering. Essentially, your CMS then manages your content, but instead of rendering it, it provides an API so something else can consume it.
The reason Craft’s “live preview” feature worked is that the CMS had control over the whole editing ⟷ previewing loop.
Now with something else doing the rendering, that’s no longer the case. So they had come up with a clever solution.
Link Tokenized Preview
The solution the fine folks at Pixel & Tonic came up with is a combination of auto-saved entry drafts and a token that’s sent along to the web page that is being previewed.
When you click on Preview, roughly the following happens:
- A draft of the entry you’re editing is saved
- A token is generated for that draft, and information about the draft entry element is saved to the database
- The token is sent along to wherever the web page happens to be as a token URL param
- The web page then sends back that same token with any API requests
It looks roughly like this:
So why all of this token nonsense? Remember, we’re previewing an auto-saved draft of the entry that’s being edited.
It’s done this way because the content editor and content renderer no longer share any state, so the saved draft is that state.
The token is what Craft uses to link a preview web request to the auto-saved entry draft.
As you’re editing content with preview open, the draft entry is being regularly saved
When a request comes in to Craft that has a token in the URL params, roughly the following happens:
- Craft looks up the route information associated with the token in the tokens database table
- In the case of headless preview, the Preview controller’s actionPreview() method is called
- The auto-saved draft element that’s being previewed is then added to a list of placeholder elements
- Whenever an element query is done that would match any placeholder elements, they are swapped in
This is what that causes it all to “just work”. Check out the ElementQuery methods _placeholderCondition() and _createElement().
Since the token was passed down to the web page that’s being previewed, if it is passed back up to the API that retrieves data from Craft, the placeholder elements get mocked in.
Just like magic.
Link Make it so
The Live preview using Vue.js post details it pretty well in terms of what you need to do to add support on your end.
Essentially, it boils down to just extracting the token URL param, and sending it back to the Craft CMS API endpoint, whether that be Element API, CraftQL plugin, or whatever else you may be using.
Here’s some JavaScript that Brandon Kelly posted:
// Get the preview token from the URL
let m = document.location.href.match(/\btoken=([^&]+)/);
let token = m ? m[1] : '';
// Then forward that on whenever you are sending an API request
let url = `...?token=${token}`;
// ...
That’s really all there is to it. Extract the token URL param, and send it back with your API calls.
If you send Craft back the token it sent you, it’ll take care of the rest
The x-craft-preview URL param & request header is just a way you can distinguish the request definitively as a Craft preview, since you might be using the token URL param for other things as well.
There is a tokenParam general config setting if you need to change it to something other than the default of token.
This exact same technique is also used for Share links as well!
If you’re concerned about preview not remembering the scroll position cross-domain, check out Clive’s ScrollMemNonEs6.js gist.
Happy headless previewing!