RaccoonLaptopGif

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;