Project setup
Blok doesn’t force a layout, but this is the one most teams settle on.
app/
├─ editor/
│ └─ page.tsx // the <Blok /> mount
├─ preview/
│ └─ [slug]/page.tsx // renders saved Data as a real page
├─ api/
│ └─ pages/route.ts // POST: save, GET: list
blok/
├─ config.ts // Config object (shared with preview)
├─ components/ // block renderers (used by editor AND preview)
│ ├─ Hero.tsx
│ ├─ Features.tsx
│ └─ ...
└─ plugins/
└─ my-plugin.tsShare renderers between editor and preview
The trick most apps want: the same block components should render in the editor and on the real, published page. Put them in their own folder and import from both places.
blok/components/Hero.tsx
export function Hero({ title, subtitle }: { title: string; subtitle: string }) {
return (
<section>
<h1>{title}</h1>
<p>{subtitle}</p>
</section>
);
}blok/config.ts
import { Hero } from "./components/Hero";
export const config: Config = {
components: {
Hero: {
label: "Hero",
fields: { /* ... */ },
render: Hero,
},
},
};Now your preview page can render the same component without going through the editor:
app/preview/[slug]/page.tsx
import { config } from "@/blok/config";
export default async function Preview({ params }) {
const data = await loadPageBySlug(params.slug);
return (
<>
{data.content.map((block) => {
const C = config.components[block.type].render;
return <C key={block.props.id} {...block.props} />;
})}
</>
);
}Save the document
Use the onSave and onPublish callbacks to persist. A typical REST flow:
<Blok
config={config}
data={initialData}
onSave={async (data) => {
await fetch(`/api/pages/${pageId}/draft`, {
method: "PUT",
body: JSON.stringify(data),
});
}}
onPublish={async (data) => {
await fetch(`/api/pages/${pageId}/publish`, {
method: "POST",
body: JSON.stringify(data),
});
}}
/>Or use @useblok/sdk’s useBlokDocument hook for a ready-made binding
that wires up save/publish/versions/comments/audit/presence to a
BlokStorageAdapter. See Real-time bindings.
TypeScript
Blok is TypeScript-first. The most useful types:
import type {
Blok,
Config,
Data,
ComponentConfig,
Fields,
Field,
BlockInstance,
RootData,
} from "@useblok/core";Fields<Props> and ComponentConfig<Props> let you get prop-level
autocomplete in render, defaultProps, and getSummary.
Last updated on