The following guide requires tinacms: 1.0.2 or later.
Want to skip to the end result? Check out the final result
In most cases, you will not want to create pages on your production site for your draft documents. This makes handling drafts a challenge with visual editing. In this example we will show how to add visual editing to a draft document using Next.js preview-mode.
In preview-mode getStaticProps
will be called on every request. This means that we can conditionally grab draft documents in preview-mode, and keep them out of your production site.
"Preview-mode" can be added in just a few steps:
Note: If you have not installed@tinacms/auth
you can do so by runningyarn add @tinacms/auth
ornpm install @tinacms/auth
Create a file called pages/api/preview/enter.{ts,js}
this will handle the request to enter preview-mode. This file should look like this:
import { isUserAuthorized } from '@tinacms/auth'const handler = async (req, res) => {if (process.env.NODE_ENV === 'development') {// Enter preview-mode in local developmentres.setPreviewData({})return res.redirect(req.query.slug)}// Check TinaCloud tokenconst isAuthorizedRes = await isUserAuthorized({token: `Bearer ${req.query.token}`,clientID: process.env.NEXT_PUBLIC_TINA_CLIENT_ID,})if (isAuthorizedRes) {res.setPreviewData({})return res.redirect(req.query.slug)}return res.status(401).json({ message: 'Invalid token' })}export default handler
This handler verifies (With TinaCloud) that the token is valid and then redirects to the document we want to edit.
Next, create a file called pages/api/preview/exit.{ts,js}
this will handle the request to exit preview-mode. This file should look like this:
const handler = (req, res) => {res.clearPreviewData()res.redirect(req.query.slug)}export default handler
Both of these files are based on the Next.js preview-mode api handlers.
tina/config
export default defineConfig({// ...// Add this to your configadmin: {auth: {onLogin: async ({ token }) => {// When the user logs in enter preview modelocation.href =`/api/preview/enter?token=${token.id_token}&slug=` + location},onLogout: async () => {// When the user logs out exit preview modelocation.href = `/api/preview/exit?slug=` + location},},},// ...})
We'll now update our getStaticPaths
, so that draft pages are excluded in our production site.
const req = await client.queries.postConnection()
export const getStaticPaths = async () => {- const req = await client.queries.postConnection()+ const req = await client.queries.postConnection({+ filter: { draft: { eq: false } },+ })// ...}
Depending on your use case you can also safely use any value for fallback
.
First we will create a util function that will either return all the documents or just the production documents depending on if we are in preview-mode.
util/getPosts.{ts,js}
import { client } from '../<PathToTina>/tina/__generated__/client'export const getPosts = async ({ preview }) => {// by default get non-draft postslet filter = { draft: { eq: false } }// if preview-mode is enabled, get all postsif (preview) {filter = {}}return client.queries.postConnection({filter,})}
Use this function anywhere you are fetching a list of posts (Posts index page).
import { getPosts } from '../util/getPosts'//...export const getStaticProps = async ({ preview = false }) => {const { data, query, variables } = await getPosts({preview,})return {props: {preview,data,query,variables,//myOtherProp: 'some-other-data',},}}
On pages that use SSR or "incremental static regeneration" (ISR), your getStaticProps
function will be called on every request. This means that we need to return a 404 when the document is a draft and we are not in preview-mode.
export const getStaticProps = async ({ params, preview = false }) => {const { data, query, variables } = await client.queries.post({relativePath: params.slug + '.md',})return {// the post is not found if its a draft and the preview is falsenotFound: data?.post?.draft && !preview,props: {preview,data,query,variables,},}}
To help with exiting preview-mode we can add a button to the top of the site. The button will show up in any page that returns preview: true
in getStaticProps
.
In pages/_app.{ts,js}
add the following:
const App = ({ Component, pageProps }) => {const slug = typeof window !== 'undefined' ? window.location.pathname : '/'return (<>{/* Feel free to add your own styling! */}{pageProps.preview && (<div>You are in preview-mode{/* This link will logout of Tina and exit preview mode */}<ahref={`/admin/index.html#/logout?slug=/api/preview/exit?slug=${slug}`}>Click here</a>{' '}to exit</div>)}<Component {...pageProps} /></>)}export default App
Now when an editor logs in they will enter preview mode and be able to contextual edit draft documents.
You can see the final result here and if you want to learn more about preview mode see the Next.js docs.
If you're using editorial workflows, you'll likely want to ensure that the preview data is fetching content from the branch
you're editing. To enable this, subscribe to the branch:change
event via the cmsCallback
function in the config:
// tina/config.tsimport { defineConfig } from 'tinacms'export default defineConfig({// ...cmsCallback: (cms) => {cms.events.subscribe('branch:change', async ({ branchName }) => {console.log(`branch change detected. setting branch to ${branchName}`)return fetch(`/api/preview/change-branch?branchName=${branchName}`)})return cms},})
Add another api endpoint at /api/preview/change-branch
, which only updates the branch data if we're
in preview mode:
// api/preview/change-branchexport default function handler(req, res) {if (req.preview && req.query?.branchName) {res.setPreviewData({ branch: req.query.branchName })return res.status(200).json({ message: 'Success' })}return res.status(403).json({ message: 'Unauthorized' })}
Update our request to include the branch (if provided) in getStaticProps
:
export const getStaticProps = async ({params,preview = false,previewData = {},}) => {const { data, query, variables } = await client.queries.post({relativePath: params.slug + '.md',},{branch: preview && previewData?.branch,})return {// the post is not found if its a draft and the preview is falsenotFound: data?.post?.draft && !preview,props: {preview,data,query,variables,},}}
© TinaCMS 2019–2024