
Storyblok Headless CMS
参考: https://www.storyblok.com/technologies#nextjs
描述: 一个CMS系统。
1. Install the SDK
In your terminal, run the following command:
npm i @storyblok/react
2. Configure the SDK
Inpages/_app.js
, load the integration and provide the access token of your Storyblok space.
// pages/_app.js
import { storyblokInit, apiPlugin } from "@storyblok/react";
storyblokInit({
accessToken: "<your-access-token>",
use: [apiPlugin]
});
3. Fetching a Story
Inpages/index.js
, fetch the home story of your Storyblok space.
// pages/index.js
import Head from "next/head"
import styles from "../styles/Home.module.css"
import { getStoryblokApi } from "@storyblok/react"
export default function Home(props) {
return (
<div className={styles.container}>
<Head>
<title>Create Next App</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<header>
<h1>
{ props.story ? props.story.name : 'My Site' }
</h1>
</header>
<main>
</main>
</div>
)
}
export async function getStaticProps() {
// home is the default slug for the homepage in Storyblok
let slug = "home";
// load the draft version
let sbParams = {
version: "draft", // or 'published'
};
const storyblokApi = getStoryblokApi();
let { data } = await storyblokApi.get(`cdn/stories/${slug}`, sbParams);
return {
props: {
story: data ? data.story : false,
key: data ? data.story.id : false,
},
revalidate: 3600, // revalidate every hour
};
}
4. Create Storyblok components
Create the counterparts to the components defined in your Storyblok space. StoryblokComponent
handles all of your nestable blocks automatically.
// components/Page.js
import { storyblokEditable, StoryblokComponent } from "@storyblok/react";
const Page = ({ blok }) => (
<main {...storyblokEditable(blok)}>
{blok.body.map((nestedBlok) => (
<StoryblokComponent blok={nestedBlok} key={nestedBlok._uid} />
))}
</main>
);
export default Page;
Example (In Next.js v14)
Install
pnpm add @storyblok/react
Set Layout
File: layout.tsx
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "../tailwind.css";
import BaseLayout from "~/layouts/base-layout.tsx";
import StoryblokProvider from "~/components/cms/storyblok-provider.tsx";
import { storyblokInit, apiPlugin } from "@storyblok/react/rsc";
import StoryblokBridgeLoader from "@storyblok/react/bridge-loader";
import Feature from "~/components/cms/feature.tsx";
import Grid from "~/components/cms/grid.tsx";
import Flex from "~/components/cms/flex.tsx";
import Teaser from "~/components/cms/teaser.tsx";
import Page from "~/components/cms/page.tsx";
import Card from "~/components/cms/card.tsx";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
storyblokInit({
accessToken: "NgIG1ovIfahkIKd0XmzdRAtt",
use: [apiPlugin],
components: {
feature: Feature,
grid: Grid,
flex: Flex,
teaser: Teaser,
page: Page,
card: Card,
},
});
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={inter.className}>
<BaseLayout>{children}</BaseLayout>
<StoryblokBridgeLoader options={{}} />
</body>
</html>
);
}
Set Page
File: app/page.tsx
import { getStoryblokApi, StoryblokComponent } from "@storyblok/react/rsc";
export default async function Home() {
const { data } = await fetchData();
return (
<div>
<StoryblokComponent blok={data.story.content} />
</div>
);
}
export async function fetchData() {
let slug = "home";
let sbParams = { version: "draft" };
const storyblokApi = getStoryblokApi();
return await storyblokApi.get(`cdn/stories/${slug}`, sbParams);
}
Set Components
File: card.tsx
import { storyblokEditable } from "@storyblok/react";
const Card = ({
blok,
}: {
blok: {
image: {
filename: string;
};
name: string;
price: string;
};
}) => {
console.log("blok.image", blok.image);
return (
<>
<img
className="text-2xl mb-10"
{...storyblokEditable(blok)}
{...blok.image}
src={blok.image.filename}
/>
<h2 className="text-2xl mb-10" {...storyblokEditable(blok)}>
{blok.name}
</h2>
<p className="text-2xl mb-10" {...storyblokEditable(blok)}>
{blok.price}
</p>
</>
);
};
export default Card;
File: feature.tsx
import { storyblokEditable } from "@storyblok/react";
const Feature = ({
blok,
}: {
blok: {
name: string;
};
}) => (
<div className="column feature" {...storyblokEditable(blok)}>
{blok.name}
</div>
);
export default Feature;
File: flex.tsx
import { storyblokEditable, StoryblokComponent } from "@storyblok/react";
const Flex = ({
blok,
}: {
blok: {
columns: any[];
};
}) => {
return (
<div className="flex items-center" {...storyblokEditable(blok)}>
{blok.columns.map((nestedBlok) => (
<StoryblokComponent blok={nestedBlok} key={nestedBlok._uid} />
))}
</div>
);
};
export default Flex;
File: grid.tsx
import { storyblokEditable, StoryblokComponent } from "@storyblok/react";
const Grid = ({
blok,
}: {
blok: {
columns: any[];
};
}) => {
return (
<div className="grid grid-cols-3" {...storyblokEditable(blok)}>
{blok.columns.map((nestedBlok) => (
<StoryblokComponent blok={nestedBlok} key={nestedBlok._uid} />
))}
</div>
);
};
export default Grid;
File: page.tsx
import { storyblokEditable, StoryblokComponent } from "@storyblok/react";
const Page = ({
blok,
}: {
blok: {
body: any[];
};
}) => (
<main className="text-center mt-4" {...storyblokEditable(blok)}>
{blok.body.map((nestedBlok) => (
<StoryblokComponent blok={nestedBlok} key={nestedBlok._uid} />
))}
</main>
);
export default Page;
File: teaser.tsx
import { storyblokEditable } from "@storyblok/react";
const Teaser = ({
blok,
}: {
blok: {
headline: string;
};
}) => {
return (
<h2 className="text-2xl mb-10" {...storyblokEditable(blok)}>
{blok.headline}
</h2>
);
};
export default Teaser;