Packages
@httpjpg/storyblok-utils
Utility functions for Storyblok integration
@httpjpg/storyblok-utils
Helper functions for Storyblok image processing, link resolution, asset handling, and slug generation.
Installation
pnpm add @httpjpg/storyblok-utilsFunctions
Dynamic Component Rendering
DynamicStoryblokComponent
Dynamically render Storyblok components:
import { DynamicStoryblokComponent } from '@httpjpg/storyblok-utils';
export function ContentArea({ bloks }) {
return (
<>
{bloks.map((blok) => (
<DynamicStoryblokComponent key={blok._uid} blok={blok} />
))}
</>
);
}Image Processing
getImageDimensions()
Extract width and height from Storyblok image URL:
import { getImageDimensions } from '@httpjpg/storyblok-utils';
const dimensions = getImageDimensions('https://a.storyblok.com/f/123/1920x1080/image.jpg');
// { width: 1920, height: 1080 }optimizeStoryblokImage()
Generate optimized image URL with transformations:
import { optimizeStoryblokImage } from '@httpjpg/storyblok-utils';
const optimized = optimizeStoryblokImage(
'https://a.storyblok.com/f/123/image.jpg',
{
width: 800,
height: 600,
quality: 80,
format: 'webp',
fit: 'cover',
}
);
// Returns: https://a.storyblok.com/f/123/800x600/filters:quality(80):format(webp)/image.jpgOptions:
width- Target widthheight- Target heightquality- Image quality (1-100)format- Output format (webp, jpg, png)fit- Resize mode (cover, contain, fill)focus- Focus point (center, top, bottom, left, right)
Link Resolution
resolveLinkUrl()
Convert Storyblok link to Next.js URL:
import { resolveLinkUrl } from '@httpjpg/storyblok-utils';
// Internal story link
const url = resolveLinkUrl({
linktype: 'story',
cached_url: 'about/team',
});
// Returns: '/about/team'
// External URL
const external = resolveLinkUrl({
linktype: 'url',
url: 'https://example.com',
});
// Returns: 'https://example.com'
// Email link
const email = resolveLinkUrl({
linktype: 'email',
email: 'hello@example.com',
});
// Returns: 'mailto:hello@example.com'getLinkTarget()
Get target attribute for links:
import { getLinkTarget } from '@httpjpg/storyblok-utils';
const target = getLinkTarget({
linktype: 'url',
target: '_blank',
});
// Returns: '_blank'Asset Handling
getAssetType()
Determine asset type from filename:
import { getAssetType } from '@httpjpg/storyblok-utils';
getAssetType('video.mp4'); // 'video'
getAssetType('image.jpg'); // 'image'
getAssetType('file.pdf'); // 'document'
getAssetType('song.mp3'); // 'audio'isVideoAsset()
Check if asset is a video:
import { isVideoAsset } from '@httpjpg/storyblok-utils';
isVideoAsset({ filename: 'video.mp4' }); // true
isVideoAsset({ filename: 'image.jpg' }); // falsegetVideoThumbnail()
Generate video thumbnail URL:
import { getVideoThumbnail } from '@httpjpg/storyblok-utils';
const thumb = getVideoThumbnail('https://a.storyblok.com/f/123/video.mp4');
// Returns: https://a.storyblok.com/f/123/video.mp4/m/thumb.jpgSlug Utilities
generateSlug()
Generate URL-safe slug from text:
import { generateSlug } from '@httpjpg/storyblok-utils';
generateSlug('Hello World!'); // 'hello-world'
generateSlug('Über uns & Kontakt'); // 'ueber-uns-kontakt'
generateSlug(' Multiple Spaces '); // 'multiple-spaces'slugToTitle()
Convert slug to readable title:
import { slugToTitle } from '@httpjpg/storyblok-utils';
slugToTitle('hello-world'); // 'Hello World'
slugToTitle('about-us'); // 'About Us'
slugToTitle('my-blog-post'); // 'My Blog Post'Relations
resolveRelations()
Resolve related stories:
import { resolveRelations } from '@httpjpg/storyblok-utils';
const story = await fetchStory();
const resolved = resolveRelations(story, ['author', 'category']);Logging
logger
Development logger with styled output:
import { logger } from '@httpjpg/storyblok-utils';
logger.info('Fetching story...');
logger.success('Story fetched!');
logger.warning('Slow response');
logger.error('Failed to fetch', error);Only logs in development mode.
Complete Examples
Image Component with Optimization
import { optimizeStoryblokImage, getImageDimensions } from '@httpjpg/storyblok-utils';
import Image from 'next/image';
export function StoryblokImage({ asset, alt, priority = false }) {
const dimensions = getImageDimensions(asset.filename);
return (
<Image
src={optimizeStoryblokImage(asset.filename, {
width: 1200,
quality: 85,
format: 'webp',
})}
alt={alt}
width={dimensions.width}
height={dimensions.height}
priority={priority}
/>
);
}Link Component
import { resolveLinkUrl, getLinkTarget } from '@httpjpg/storyblok-utils';
import Link from 'next/link';
export function StoryblokLink({ link, children }) {
const href = resolveLinkUrl(link);
const target = getLinkTarget(link);
const isExternal = link.linktype === 'url';
if (isExternal) {
return (
<a href={href} target={target} rel="noopener noreferrer">
{children}
</a>
);
}
return <Link href={href}>{children}</Link>;
}Video Player with Thumbnail
import {
isVideoAsset,
getVideoThumbnail,
getAssetType
} from '@httpjpg/storyblok-utils';
export function MediaPlayer({ asset }) {
if (!isVideoAsset(asset)) {
return <Image src={asset.filename} alt={asset.alt} />;
}
return (
<video
src={asset.filename}
poster={getVideoThumbnail(asset.filename)}
controls
/>
);
}Breadcrumbs from Slug
import { slugToTitle } from '@httpjpg/storyblok-utils';
export function Breadcrumbs({ path }) {
const segments = path.split('/').filter(Boolean);
return (
<nav>
{segments.map((segment, i) => (
<span key={i}>
{slugToTitle(segment)}
{i < segments.length - 1 && ' / '}
</span>
))}
</nav>
);
}TypeScript Types
import type {
StoryblokAsset,
StoryblokLink,
ImageTransformOptions,
} from '@httpjpg/storyblok-utils';Best Practices
- Always optimize images before rendering
- Use webp format for better compression
- Handle external links with proper rel attributes
- Log errors in development for debugging
- Type your components with Storyblok types
- Cache optimized URLs to reduce processing