datocms-plugin-web-previews v1.0.16
Web Previews DatoCMS plugin
This plugin adds side-by-side previews, and quick links in the record sidebar to preview your webpages.
🚨 Important: This is not a drag & drop plugin! It requires a lambda function on your frontend website(s) in order to function. Read more in the following sections!
Installation and configuration
Once the plugin is installed you need to specify:
- A list of frontends. Each frontend specifies a name and a preview webhook, which will be called as soon as the plugin is loaded. Read more about it on the next chapter.
- Sidebar open: to specify whether you want the sidebar panel to be opened by default.
⚠️ For side-by-side previews to work, if your website implements a Content Security Policy frame-ancestors
directive, you need to add https://plugins-cdn.datocms.com
to your list of allowed sources, ie.:
Content-Security-Policy: frame-ancestors 'self' https://plugins-cdn.datocms.com;
The Previews webhook
Each frontend must implement a CORS-ready JSON endpoint that, given a specific DatoCMS record, returns an array of preview link(s).
The plugin performs a POST request to the Previews webhook URL, passing a payload that includes the current environment, record and model:
{
"item": {…},
"itemType": {…},
"currentUser": {…},
"environmentId": "main",
"locale": "en",
}
item
: CMA entity of the current recorditemType
: CMA entity of the model of the current recordcurrentUser
: CMA entity of the collaborator, SSO user or account owner currently logged inenvironmentId
: the current environment IDlocale
: the locale currently active on the form
The endpoint is expected to return a 200
response, with the following JSON structure:
{
"previewLinks": [
{
"label": "Published (en)",
"url": "https://mysite.com/blog/my-article"
},
{
"label": "Draft (en)",
"url": "https://mysite.com/api/preview/start?slug=/blog/my-article"
}
]
}
The plugin will display all the returned preview links.
Implementation examples
If you have built alternative endpoint implementations for other frameworks/SSGs, please open up a PR to this plugin and share it with the community!
Next.js
We suggest you look at the code of our official Next.js demo:
- Endpoint called by this plugin, returning the preview links:
app/api/draft/preview-links/route.tsx
- Endpoint to enter Next.js Draft Mode:
app/draft/enable/route.tsx
- Endpoint to exit Next.js Draft Mode:
app/draft/disable/route.tsx
Nuxt 3
Below here, you'll find a similar example, adapted for Nuxt. For the purpose of this example, let's say we want to return a link to the webpage that contains the published content.
If you deploy on a provider that supports edge functions, Nuxt 3 applications can expose a dynamic API: files in the /server/api
folders will be converted into endpoints. So it's possible for DatoCMS to make a POST request to the Nuxt app with the info about the current record. What we'll actually do, is to implement a CORS enabled API endpoint returning an array of preview links built on the base of the record, the item type and so on:
// Put this code in the /server/api directory of your Nuxt website (`/server/api/preview-links.ts` will work):
// this function knows how to convert a DatoCMS record into a canonical URL within the website.
// this function knows how to convert a DatoCMS record
// into a canonical URL within the website
const generatePreviewUrl = ({ item, itemType, locale }) => {
switch (itemType.attributes.api_key) {
case 'landing_page':
return `/landing-pages/${item.attributes.slug}`;
case 'blog_post':
// blog posts are localized:
const localePrefix = locale === 'en' ? '' : `/${locale}`;
return `${localePrefix}/blog/${item.attributes.slug[locale]}`;
default:
return null;
}
};
export default eventHandler(async (event) => {
// In this method, we'll make good use of the utility methods that
// H3 make available: they all take the `event` as first parameter.
// For more info, see: https://github.com/unjs/h3#utilities
// Setup content-type and CORS permissions.
setResponseHeaders(event, {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST',
'Access-Control-Allow-Headers': 'Content-Type, Authorization', // add any other headers you need
})
// This will allow OPTIONS request
if (event.req.method === 'OPTIONS') {
return send(event, 'ok')
}
// Actually generate the URL using the info that DatoCMS is sending.
const url = generatePreviewUrl(await readBody(event))
// No URL? No problem: let's send back no link.
if (!url) {
return { previewLinks: [] }
}
// Let's guess the base URL using environment variables:
// if you're not working with Vercel or Netlify,
// ask for instructions to the provider you're deploying to.
const baseUrl = process.env.VERCEL_BRANCH_URL
? // Vercel auto-populates this environment variable
`https://${process.env.VERCEL_BRANCH_URL}`
: // Netlify auto-populates this environment variable
process.env.URL
// Here is the list of links we're returnig to DatoCMS and that
// will be made available in the sidebar of the record editing page.
const previewLinks = [
// Public URL:
{
label: 'Published version',
url: `${baseUrl}${url}`,
},
]
return { previewLinks }
})
SvelteKit 2
Below here, you'll find a similar example, adapted for SvelteKit. For the purpose of this example, let's say we want to return a link to the webpage that contains the published content.
Create a +server.ts
file under src/routes/api/preview-links/
with following contents:
import { json } from '@sveltejs/kit';
const generatePreviewUrl = ({ item, itemType, locale }: any) => {
switch (itemType.attributes.api_key) {
case 'landing_page':
return `/landing-pages/${item.attributes.slug}`;
case 'blog_post':
// blog posts are localized:
const localePrefix = locale === 'en' ? '' : `/${locale}`;
return `${localePrefix}/blog/${item.attributes.slug[locale]}`;
case 'post':
return `posts/${item.attributes.slug}`;
default:
return null;
}
};
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Content-Type': 'application/json'
};
export async function OPTIONS() {
setHeaders(corsHeaders);
return json('ok');
}
export async function POST({ request, setHeaders }) {
setHeaders(corsHeaders);
const data = await request.json();
const url = generatePreviewUrl(data);
if (!url) {
return json({ previewLinks: [] });
}
const baseUrl = process.env.VERCEL_BRANCH_URL
? // Vercel auto-populates this environment variable
`https://${process.env.VERCEL_BRANCH_URL}`
: // Netlify auto-populates this environment variable
process.env.URL;
const previewLinks = [
// Public URL:
{
label: 'Published version',
url: `${baseUrl}${url}`
}
];
return json({ previewLinks });
}