Decide where the pages of a Core dna site are composed before writing code: on the platform, in your own application, or on the platform with a few pieces filled in by the browser. The decision tree, each shape in depth, multi-brand and languages, and a table and case studies to make the call.
L2
Unit 02 · Headless, over the three API surfaces
Drafts, preview and knowing when content changed
Headless, over the three API surfacesPage 8 of 29Updated
Why a headless front end shows drafts to the public unless every request says otherwise, how editors preview your front end without a public URL, and how a job tells your front end that content changed, scheduled publishing included.
Who this is for. The developer of a headless front end, before it goes live. After this page your public reads leave drafts out, editors can preview a draft in your front end from the admin, and your cache is cleared when content changes rather than when it expires.
A backend credential sees drafts by default
A visitor and a signed-in member never see an unpublished record. A backend user, which is what your front end's server calls the API as, follows different rules:
Request
Unpublished records
A list, on a site with Show unpublished on (the default)
Included
A list with retrieveUnpublished=0
Left out
A list with retrieveUnpublished=1
Included, whatever the setting
One record by its numeric id
Returned, whatever the parameter or the setting
One page by its path
As for a list
Show unpublished is a site setting (Globals.admin.showUnpublishedRecords) that lets signed-in admins see drafts on the site and through the API. This site has it on, and an unpublished book shows how a public read goes wrong:
An unpublished book, read with a backend credential
GET /cdna-api/books/books?id=12&properties={id,name,published} HTTP/1.1
Host: hub.coredna.com
Authorization: Bearer YOUR_ACCESS_TOKEN
Accept: application/json
So two rules for every read that feeds a public page: send retrieveUnpublished=0, and when you read a record by its id, check that published is set to a time that has passed before showing it. Put both in the one module that makes your API calls, so no call site can forget them.
Preview without a public URL
Editors preview a record from its editing form in the admin. Normally the admin renders the record through the site's templates; for a headless front end it can load a page of yours instead. Set the site setting Alternative live preview url (Globals.admin.livePreviewUrl) to your preview route, with any of three placeholders the admin fills in:
When an editor opens the preview, the admin first saves the form as a draft revision, then loads that URL in its preview pane. Your route then:
Checks the secret. The URL is visible to editors, not to the public, and the secret is what makes the route impossible to guess. Without the right key, answer 404.
Reads the draft with its backend credential, by id, for example GET /cdna-api/pages/page/{id} with properties={id,lastRevision{id,name,areasContent}}. lastRevision is the newest revision, the draft just saved; check that its id is the one in the URL.
Renders it with your normal page code, and sends Cache-Control: no-store so that neither your cache nor a CDN keeps it.
The admin uses this route only for records with revisions, which is every page, post, book page and the other versioned types. What does not come back is in-context editing: in the platform's own preview an editor can type into the rendered page, and your front end's preview is read-only.
Knowing when content changed
A job (formerly a hook) tells your front end. Give it entity events on the record types your front end shows, CreateRecord, UpdateRecord and DeleteRecord, and one action: an HTTP request to a revalidation endpoint of yours, with a secret header and a body that names the record. The body is a template in the site's engine:
Template codeThe HTTP request action's body
{* Every value goes through json_encode so the body stays valid JSON *}
{
"entity": {$event->getEntityName()|json_encode},
"event": {$event->getMethod()|json_encode},
"id": {$event->getRecordId()|json_encode}
}
{# Every value goes through json_encode so the body stays valid JSON #}
{
"entity": {{ event.getEntityName()|json_encode }},
"event": {{ event.getMethod()|json_encode }},
"id": {{ event.getRecordId()|json_encode }}
}
The record's id and type are enough for your front end to drop what it cached; reading the record itself in the body fails for a DeleteRecord, when it is gone. Expect one event per save, and debounce on your side if editors save often.
Scheduled publishing is covered too. A platform task runs every minute and, for each record whose publish date has passed since the last run, fires UpdateRecord: pages, book pages, units, books, blogs, posts, FAQ questions and product descriptions. Pages, books, units and book pages get the same event when their unpublish date passes. A page scheduled for nine o'clock reaches your front end within about a minute of it, through the same job. Events, what starts a job lists the events and how to limit a job to the records it should hear about.
Test signed out, from outside
A public page that shows a draft looks correct to everyone who tests it with an admin's credentials. Check the live front end with a record you have just unpublished, and confirm it disappears.