Skip to content

Instantly share code, notes, and snippets.

@SimeonGriggs
Last active December 23, 2023 23:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SimeonGriggs/6649dc7f4b0fec974c05d29cae969cbc to your computer and use it in GitHub Desktop.
Save SimeonGriggs/6649dc7f4b0fec974c05d29cae969cbc to your computer and use it in GitHub Desktop.
Sanity + Next.js Preview API Route with Config for SEO Pane
// ./pages/api/preview.ts
import { groq } from "next-sanity";
import { previewClient } from "../../lib/sanity.client";
import { NextApiRequest, NextApiResponse } from "next";
export const STUDIO_URL_DEV = "http://localhost:3333";
export const STUDIO_URL_PROD = "https://replace-with-yours.sanity.studio";
export const WEBSITE_URL_DEV = "http://localhost:3000";
export const WEBSITE_URL_PROD = "https://replace-with-yours.com";
export default async function preview(
req: NextApiRequest,
res: NextApiResponse
) {
const host = req.headers.host;
// Is the SEO plugin trying to fetch and return HTML?
// AND is the Studio on a different URL to the website?
if (req.query.fetch) {
// Allow requests from the Studio's URL
const corsOrigin = host.includes("localhost")
? // Possibly required for Node 18 which doesn't like "localhost"
// STUDIO_URL_DEV.replace("//localhost:", "//127.0.0.1:")
// Otherwise fine on Node 16
STUDIO_URL_DEV
: STUDIO_URL_PROD;
res.setHeader("Access-Control-Allow-Origin", corsOrigin);
res.setHeader("Access-Control-Allow-Credentials", "true");
}
const { id } = req?.query ?? {};
if (!id) {
return res.status(401).json({ message: "No document id provided" });
}
// Ensure this slug actually exists in the dataset
const query = groq`*[_id == $id && defined(slug.current)][0]`;
// This is an authenticated client with a token that can fetch draft ID's
const doc = await previewClient.fetch(query, { id });
if (!doc) {
return res.status(401).json({ message: "Invalid document id" });
}
const slug = doc.slug.current
// Initialise preview mode
res.setPreviewData({});
// Return just the HTML if the SEO plugin is requesting it
if (req.query.fetch) {
// Create preview URL
const baseOrigin = host.includes("localhost")
? WEBSITE_URL_DEV
: WEBSITE_URL_PROD;
const absoluteUrl = new URL(slug, baseOrigin).toString();
// Create preview headers from the setPreviewData above
const previewHeader = res.getHeader("Set-Cookie");
const previewHeaderString =
typeof previewHeader === "string" || typeof previewHeader === "number"
? previewHeader.toString()
: previewHeader?.join("; ");
const headers = new Headers();
headers.append("credentials", "include");
headers.append("Cookie", previewHeaderString ?? "");
const previewHtml = await fetch(absoluteUrl, { headers })
.then((previewRes) => previewRes.text())
.catch((err) => console.error(err));
return res.send(previewHtml);
}
// Redirect to the path from the fetched post
// We don't redirect to req.query.slug as that might lead to open redirect vulnerabilities
return res.writeHead(307, { Location: slug }).end();
}
@AbdallahAbis
Copy link

@SimeonGriggs The SEO plugin sends no data except the query param fetch=true, how api/preview is supposed to get the document ID then? this API will always return 401

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment