
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
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:
- Capture the request
- Check for a token and sourceUid
- Query for an entry
- Trigger Preview mode
- 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)}}
gql/entries.js
import{gql}from'@apollo/client'exportconstENTRIES_DATA=gql` query ($uid: [String!]) { entry(uid: $uid, limit: 1) { title uri url slug uid } }`;
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;
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 } } }`;
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}
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)
For further actions, you may consider blocking this person and/orreporting abuse