@supernaut/storyblok-tools
v1.1.1
Published
Storyblok tools for Next.js
Maintainers
Readme
Storyblok Tools
Tools to simplify recurring tasks when working with Storyblok in Next.js.
Usage
Install the package using your package manager of choice:
pnpm add @supernaut/storyblok-toolsnpm install @supernaut/storyblok-toolsyarn add @supernaut/storyblok-toolsCheck If Request Comes from Storyblok
This function checks whether an incoming request URL originates from Storyblok's visual editor ("in-editor" / preview mode) by validating the signed query parameters Storyblok appends to iframe / bridge requests.
Validation steps performed:
- Ensures a request URL and a Storyblok preview token are present. The preview token
is taken from the optional
storyblokTokenargument or theSTORYBLOK_PREVIEW_TOKENenvironment variable. - Verifies the URL contains the required query parameters:
_storyblok_tk[token](HMAC-like validation hash Storyblok generates)_storyblok_tk[space_id](numeric space identifier)_storyblok_tk[timestamp](unix timestamp in seconds)
- Recomputes the expected validation token using SHA-1 over the string
${space_id}:${previewToken}:${timestamp} - Compares the recomputed token with the provided one AND checks that the timestamp is not older than 1 hour (3600 seconds).
Any thrown error or failed validation path results in false.
This is heavily inspired by the work of Jorge Martins in this gist
import { isStoryblokRequest } from "@supernaut/storyblok-tools";
function requestHandler(request: Request) {
const isFromStoryblok = await isStoryblokRequest(request.url);
if (!isFromStoryblok) {
throw new Error("Request was not from Storyblok preview!");
}
}Example Next.js Route Handler for Storyblok Preview
Given that you put the handler in app/api/draft/route.ts and your public website is https://www.example.com/ you can then provide this as the preview URL for the storyblok visual editor: https://www.example.com/api/draft?slug=
This route handler only works with the Next.js app router.
import { isStoryblokRequest } from "@supernaut/storyblok-tools";
return async function GET(request: Request) {
const isFromStoryblok = await isStoryblokRequest(request.url);
const requestUrl = new URL(request.url);
const { searchParams } = requestUrl;
const slug = searchParams.get("slug");
if (!isFromStoryblok || !slug) {
redirect("/api/end-draft");
}
if (isFromStoryblok) {
// Set URL
const url = new URL(requestUrl);
url.pathname = `/${slug}`;
// Enable draft mode
const draftObject = await draftMode();
draftObject.enable();
// Modify draft cookie for Storyblok
const cookiesObject = await cookies();
const draftCookie = { ...cookiesObject.get("__prerender_bypass") };
if (draftCookie) {
cookiesObject.set({
httpOnly: true,
name: "__prerender_bypass",
path: "/",
sameSite: "none",
secure: true,
value: draftCookie.value ?? "",
});
}
// Redirect to correct location
return NextResponse.redirect(url, 307);
}
};Exmaple Next.js Route Handler for Ending Preview
This route handler only works with the Next.js app router.
return async function GET(request: Request) {
// Disable draft mode
const draftModeObject = await draftMode();
draftModeObject.disable();
// Modify draft cookie for Storyblok
const cookiesObject = await cookies();
const draftCookie = { ...cookiesObject.get("__prerender_bypass") };
if (draftCookie) {
cookiesObject.set({
expires: new Date(0), // Set expiration date to the past
httpOnly: true,
name: "__prerender_bypass",
path: "/",
sameSite: "none",
secure: true,
value: draftCookie.value ?? "",
});
}
// Redirect to index
redirect("/");
};