New release: Conditional Fields

SEO Pane

By Simeon Griggs

Run yoast.js analysis on your front end, refreshing as you edit your Document, from inside a View Pane.

sanity-plugin-seo-pane

Run Yoast's SEO review tools using Sanity data, inside a List View Pane. When setup correctly, it will fetch your rendered front-end, as your Sanity data changes, to give instant SEO feedback on your document.

🚨 Important 🚨

This plugin is early, complex and it some ways convoluted. Over time it should become more simplified.

SEO Pane

Installation

sanity install seo-pane

This plugin requires a very specific setup in order to get the full benefit. It is designed to be used as a Component inside of a View.

// ./src/deskStructure.js
import SeoPane from 'sanity-plugin-seo-pane'

// ...all other list items

S.view
  .component(SeoPane)
  .options({
    keywords: `seo.keywords`,
    synonyms: `seo.synonyms`,
    url: (doc) => resolveProductionUrl(doc),
  })
  .title('SEO')

The .options() configuration works as follows:

  • keywords (string, required) A dot-notated string from the document object to a field containing the keywords/keyphrase.
  • synonyms (string, optional) As above.
  • url (function, required) A function that takes in the current document, and should return a string with a URL to a preview-enabled front-end. You likely have a function like this already for Live Preview.

Defining the content area

By default, the plugin will examine all content it finds inside a tag with this attribute: data-content="main".

If this cannot be found it will fallback to content <main>inside your main tag</main>.

Fetching the front-end

Because the plugin uses Fetch, you're likely to run into CORS issues retrieving the front end from the Studio. Therefore, you may need to do some setup work on your preview URL. If you're using Next.js, adding this to the top of of your preview /api route will make fetch happen.

Some snippets are below, but here is a full Sanity Preview Next.js API Route for reference

// ./pages/api/preview.js
const corsOrigin =
  process.env.NODE_ENV === 'development'
    ? `http://localhost:3333`
    : `https://your-studio.sanity.studio`

res.setHeader('Access-Control-Allow-Origin', corsOrigin)
res.setHeader('Access-Control-Allow-Credentials', true)

Returning the page HTML as a string

The Component will append a fetch=true parameter to the URL. You can use this to make the /api route to actually perform its own fetch for the markup of the page – not redirect to it – and return the expected object shape.

Making your Preview route actually fetch the markup and just return a string will avoid problems with having to pass cookies along from Sanity Studio, to the preview route, to the front end. You will note in the below example though we are deliberately copying the Cookies from the incoming request to the /api route and passing them along to the front-end.

// ./pages/api/preview.js

// ... CORS enabled stuff, res.setPreviewData, etc

// Fetch the preview-page's HTML and return in an object
if (req?.query?.fetch === 'true') {
  const proto = process.env.NODE_ENV === 'development' ? `http://` : `https://`
  const host = req.headers.host
  const pathname = req?.query?.slug ?? `/`
  const absoluteUrl = new URL(`${proto}${host}${pathname}`).toString()

  const previewHtml = await fetch(absoluteUrl, {
    credentials: `include`,
    headers: {Cookie: req.headers.cookie},
  })
    .then((previewRes) => previewRes.text())
    .catch((err) => console.error(err))

  return res.send(previewHtml)
}

A note on server-side rendering of draft content

As a final, Next.js specific note. Because this is going to fetch server-side, you'll need to make sure your getStaticProps() is actually going to return draft content server-side.

(Client-side, Sanity's usePreviewSubscription hook will take Published content and return a Draft version, but server-side we need to do it ourselves)

It's easy to accidentally configure Next.js and Sanity to query for only published data, and then switch over to draft content client-side.

For example, your GROQ query might look like *[slug.current == $slug][0] which will only return one document, and not necessarily the draft.

To solve this with the server side query, I'll make sure we query for all documents that match the slug (as in, draft and published) then use this function to just filter down to the one I want:

filterDataToSingleItem(data, preview) {
  if (!Array.isArray(data)) {
    return data
  }

  return data.length > 1 && preview
    ? data.filter((item) => item._id.startsWith(`drafts.`)).pop()
    : data.pop()
}

It's that easy!

License

MIT Β© Simeon Griggs See LICENSE

Install command

sanity install seo-pane

Contributor

Simeon Griggs

πŸ‡¦πŸ‡Ί in πŸ‡¬πŸ‡§ Solution Engineer @ Sanity

Simeon is located at Newcastle upon Tyne, UK
Visit Simeon Griggs's profile

Categorized in

Other plugins by author

MonkeyLearn Pane

Send the contents of a Portable Text field to MonkeyLearn's API to extract insights with the power of Machine Learning!

Simeon Griggs
Go to MonkeyLearn Pane

Iframe Pane

Display any URL in a View Pane, along with helpful buttons to Copy the URL or open in a new tab.

Simeon Griggs
Go to Iframe Pane