Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for Live preview with Craft CMS & Next.js
Myles
Myles

Posted on

     

Live preview with Craft CMS & Next.js

So you've gone headless but now content authors/editors are nagging you to preview their work before saving it?

Thankfully with Craft and Next.js live previews are possible and don't require to much effort at all.

This tutorial is based on using Craft 3.4+ and Next.js 10+ using GraphQL/Apollo with the frontend hosted on Vercel.com.

Firstly you'll need to configure your Craft sections. You'll need to configure their Preview targets to point to your frontend.

Craft Setup

Example
Alt Text

In this example I have set my preview target to{{alias('@previewBaseUrl')}}?sourceUid={sourceUid}

This Craft alias is set tohttp://example.com/api/preview

This means you will need to create a new page in Next e.g./pages/api/preview.js.

ThesourceUid that gets passed is the pages unique identifier that Craft will use.

When a user clicks preview in the CMS a unique token is passed which Craft uses to magically display the current draft in combination with the sourceUid.

By default this token is calledtoken but can be changed using the config settingtokenParam

Ok so we've configured Craft, now on to Next....

Next.js Setup

Thepreview.js file is what we'll use to trigger the Next feature Preivew Mode and Craft's Live Preview.

This file will do the following:

  1. Capture the request
  2. Check for a token and sourceUid
  3. Query for an entry
  4. Trigger Preview mode
  5. Redirect to the above entry with previewData

What Preview Mode basically does is sets a couple of cookies that tells Next that preview mode is active. This is then passed through to thecontext which we can then use to customise the content the author see's during live preview.

pages/api/preview.js

import{apollo}from'@/config/apollo'import{ENTRIES_DATA}from'@/gql/entries';exportdefaultasync(req,res)=>{// Check the token and source uidif(!req.query.token||!req.query.sourceUid){returnres.status(401).json({message:'Invalid preview request'})}// Fetch the headless CMS to check if the provided `uid` existsconstentry=awaitapollo(ENTRIES_DATA,{uid:req.query.sourceUid})// If the uid doesn't exist prevent preview mode from being enabledif(!entry){returnres.status(404).json({message:'Page not found'})}// Enable Preview Mode by setting cookiesres.setPreviewData({uid:req.query.sourceUid,token:req.query.token})// Redirect to the path from the fetched entryif(entry.data.entry.uri=='__home__'){res.redirect("/?uid="+req.query.sourceUid+"&token="+req.query.token)}else{res.redirect("/"+entry.data.entry.uri+"?uid="+req.query.sourceUid+"&token="+req.query.token)}}
Enter fullscreen modeExit fullscreen mode

gql/entries.js

import{gql}from'@apollo/client'exportconstENTRIES_DATA=gql`    query ($uid: [String!]) {        entry(uid: $uid, limit: 1) {            title            uri            url            slug            uid        }    }`;
Enter fullscreen modeExit fullscreen mode

Within the Craft CMS the iframe will first open this preview page, then if an entry is found it will redirect to that using it's uri.

Now to modify your Next.js page to use preview mode to determine what content is shown.

pages/page.js

import{apollo}from'@/config/apollo'import{ENTRY_DATA}from'@/gql/entry';functionPage(props){return(<div><h1>{props.data.entry.title}</h1><p>Myamazingwebsitesgoeshere...</p></div>)}// note this also works with getServerSidePropsexportasyncfunctiongetStaticProps(context){if(context.preview&&context.previewData.crafttoken){// Preview mode is active, pass appropiate params e.g. uid and token.var{data,errors}=awaitapollo(ENTRY_DATA,{uid:parseInt(context.previewData.sourceId)},context.previewData.crafttoken)}else{// No preview mode, return live content.var{data,errors}=awaitapollo(ENTRY_DATA)}// return a 404 if no page is foundif(errors.length>0)return{notFound:true}// return page props e.g. page data and preview data (could be useful to have in the template)return{props:{data,preview:context.preview?context.previewData:[]}}}exportdefaultPage;
Enter fullscreen modeExit fullscreen mode
import{gql}from'@apollo/client'exportconstENTRY_DATA=gql`    query ($slug: [String!], $uid: [String]) {        entry(section: "pages", slug: $slug, uid: $uid, limit: 1) {            id            title            slug            ... on pages_page_Entry {                customFieldGoesHere            }        }    }`;
Enter fullscreen modeExit fullscreen mode

In this example above you can see that ingetServerSideProps we check to see if preview mode is active and if so pass theuid and thetoken through to the GQL query using Apollo.

If it's not then we simple just query the page as normal.

context.preview will be active for as long as those cookies that Next created exist. Clearing these cookies via the Craft CMS is on my todo list 😅. Another option might be to just simply give them a short lifespan e.g. 1-5 mins (you can do this via the setPreviewData as an option).

And there you have it - live preview using Craft and statically generated pages using Next.js 🥳

Bonus

To save you some time I'll post my Apollo setup below which is used to connect to the Craft GQL API and then run the queries.

config/apollo.js

import{ApolloClient,InMemoryCache}from'@apollo/client';asyncfunctionapollo(gqlQuery,gqlParams,previewToken=false){// setup the Apollo clientconstclient=newApolloClient({// if the craft token exists then append it to the URL used to make queries// Craft uses this to load draft/revision datauri:previewToken?process.env.NEXT_PUBLIC_CMS_ENDPOINT+"?crafttoken="+previewToken:process.env.NEXT_PUBLIC_CMS_ENDPOINT,cache:newInMemoryCache()});returnawaitclient.query({// the GQL queryquery:gqlQuery,// pass through query params e.g. { slug: "boop", uid: "2dff-344d-dfdd...", token: "..." }variables:gqlParams,// display errors from GraphQL (useful for debugging)errorPolicy:'all'}).then(data=>{return{// return the data or NULL if no data is returneddata:(typeof(data.data)!="undefined")?data.data:null,// return any errorserrors:(data.errors)?data.errors:[]}});}export{apollo}
Enter fullscreen modeExit fullscreen mode
Things to note

You may run into a few issues locally if you locally environment isn't using HTTPS on both ends as many browsers these days don't like cookies that aren't secure and come from different URLS e.g. CORS.


Hopefully that gives you a good starting point and saves you some time and stress.

If you have any improvements, let me know in the comments below and I can update the article for reference.

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Senior developer using 💁‍♂️ @laravel @craftcms @vuejs @tailwindcss Next.js @vercel. #Ecommerce enthusiast 🤑 Space nerd 🚀
  • Location
    Christchurch
  • Work
    Senior Developer at Plato Creative
  • Joined

Trending onDEV CommunityHot

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp