Storyblok
Storyblok API

@natu/storyblok-api

💻 Storyblok API

A package to connect to Storyblok CMS API and fetch data.

It uses:

🤓 Usage

Import package as a dependency in package.json file

"dependencies": {
  "@natu/storyblok-api": "*",
}

Use it to connect to Storyblok and fetch data from it

page.tsx
import { draftMode } from 'next/headers';
import { notFound } from 'next/navigation';
import { relations, getStoryblokApi } from '@natu/storyblok-api';
import { DynamicRender } from '@natu/storyblok-utils';
 
const Page = async () => {
  const { isEnabled } = draftMode();
  const { getContentNode } = getStoryblokApi({ draftMode: isEnabled });
 
  const story = await getContentNode({
    slug: env.NEXT_PUBLIC_STORYBLOK_MAIN_APP_FOLDER,
    relations,
  });
 
  if (!story || !story?.ContentNode) {
    return notFound();
  }
 
  return <DynamicRender data={story.ContentNode.content} />;
};
 
export default Page;
[...slug]/page.tsx
const Page = async ({ params }: PageProps) => {
  const { isEnabled } = draftMode();
  const { getContentNode } = getStoryblokApi({ draftMode: isEnabled });
 
  const slug = getSlugWithAppName({ slug: getSlugFromParams(params.slug) });
 
  if (isSlugExcludedFromRouting(slug)) {
    return notFound();
  }
 
  const story = await getContentNode({ slug, relations });
 
  if (!story || !story?.ContentNode) {
    return notFound();
  }
 
  return <DynamicRender data={story?.ContentNode?.content} />;
};
 
export default Page;

Folders & files included

generated

A folder for files auto-generated by codegen basing on codegen.ts configuration file in root folder of the package. You can see all configuration options in the codegen documentation ↗ (opens in a new tab).

graphql

A folder for all .graphql files like queries, fragments, etc. The types in generated folder are auto-generated basing on those. Already includes queries:

  • getConfigItem - get the configuration (header, footer, notFound page etc.)
  • getContentNodeQuery - get the single story by slug
  • getContentNodesQuery - get stories with optional params (ex. sorting, filtering)
  • getLinksQuery - tbd

hooks

A folder for all reusable hooks. Already includes:

useStoryblokSdk

If you want to query data on the client side, we depend on using the useStoryblokSdk hook. It has context preview and returns the same methods as the getStoryblokApi function.

'use client';
 
import { useState } from 'react';
 
import { useStoryblokSdk } from '@natu/storyblok-api';
 
export const MyComponent = () => {
  const { getContentNode } = useStoryblokSdk();
  const [data, setData] = useState(null);
 
  return (
    <div>
      <button
        onClick={async () => {
          const story = await getContentNode({ slug: 'my-slug' });
          setData(story);
        }}
      >
        Click me!
      </button>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
};

api.ts

Contains the most generic and important function of this package: getStoryblokApi. Usage:

/apps/web/src/app/page.tsx
import { relations, getStoryblokApi } from '@natu/storyblok-api';
 
const { getContentNode } = getStoryblokApi({ <options, ex. draftMode> });
 
const story = await getContentNode({ slug: ..., relations });

relations.ts

Contains the relations between stories.

Usage:

/apps/web/src/app/page.tsx
import { relations, getStoryblokApi } from '@natu/storyblok-api';
 
const { getContentNode } = getStoryblokApi({ <options, ex. draftMode> });
 
const story = await getContentNode({ slug: ..., relations });

sdk.ts

Contains getSdk function and some connected types. Already used in api.ts file of this package. Usage:

import { env } from '@natu/env';
import { ApiFetcher } from '@natu/next-api-fetcher';
import { type Sdk, SdkFunctionWrapper, type getSdk } from '@natu/storyblok-api';
 
const { fetcher } = new ApiFetcher(
  storyblokApiUrl,
  {<fetch options ex. headers, cache>}
);
 
export const getStoryblokApi = (): Sdk  => {
  const wrapper: SdkFunctionWrapper = action => {
    return action({ <fetch options ex. headers, cache> });
  };
 
  return getSdk(fetcher, wrapper);
};

tags.ts

Contains tags which can be used ex. for Next request revalidation ↗ (opens in a new tab). Example usage:

import { TAGS, getStoryblokApi, relations } from '@natu/storyblok-api';
 
const { getConfigNode } = getStoryblokApi({ draftMode: isEnabled });
 
const configData = await getConfigNode(
  {
    slug: configSlug,
    skipNotFoundPage: true,
    skipSeo: true,
    relations,
  },
  {
    next: {
      tags: [TAGS.SB_CONFIG],
    },
  },
);

Add new query | mutation

Create new .graphql file in graphql folder

getPage.graphql
query PageItem($slug: ID!, $relations: String = "") {
  PageItem(id: $slug, resolve_relations: $relations) {
    content {
      _editable
      _uid
      body
    }
    first_published_at
    full_slug
    id
    name
    published_at
    uuid
  }
}

Run codgen script form root folder of the repository

yarn codegen

Add new query into getSdk function in sdk.ts file

sdk.ts
import { print } from 'graphql/language/printer';
 
import { ApiFetcher } from '@natu/next-api-fetcher';
 
import { GetPageItemQueryVariables, GetPageItem, GetPageItemQuery } from './generated/graphql';
 
export function getSdk(
  fetcher: ApiFetcher['fetcher'],
  withWrapper: SdkFunctionWrapper = defaultWrapper,
) {
  return {
    // ...
    getPageItem(
      variables: GetPageItemQueryVariables,
      { headers, ...restFetchOptions }: FetchOptions = {},
    ): Promise<GetPageItemQuery> {
      return withWrapper(
        async wrapperOptions => {
          const { headers: wrapperHeaders, ...restWrapperOptions } = wrapperOptions || {};
          const { data } = await fetcher<GetPageItemQuery, GetPageItemQueryVariables>({
            query: print(GetPageItem),
            variables,
            headers: {
              ...wrapperHeaders,
              ...headers,
            },
            ...restWrapperOptions,
            ...restFetchOptions,
          });
 
          return data;
        },
        'getPageItem',
        'query',
      );
    },
    // ...
    // Add more
  };
}

Use new query in your component

MyComponent.tsx
import { draftMode } from 'next/headers';
 
import { getStoryblokApi } from '@natu/storyblok-api';
 
export const MyComponent = async () => {
  const { isEnabled } = draftMode();
  const { getPageItem } = getStoryblokApi({ draftMode: isEnabled });
 
  const story = await getPageItem(
    {
      slug: 'my-slug', // Full TypeScript support for variables
    },
    {
      next: {
        tags: ['my-tag'],
        revalidate: 60,
      },
    },
  );
 
  // Full TypeScript support for response
  return <pre>{JSON.stringify(story, null, 2)}</pre>;
};