@natu/storyblok-api
💻 Storyblok API
A package to connect to Storyblok CMS API and fetch data.
It uses:
- GraphQL (opens in a new tab) query language
- codegen ↗ (opens in a new tab) to generate types
🤓 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
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;
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 sluggetContentNodesQuery
- 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:
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:
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
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
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
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>;
};