Getting started
Croct Nanostores bridges Croct’s personalization engine with Nanostores, giving you reactive, type-safe atoms that deliver personalized content to any UI framework.
Because it builds on the Nanostores ecosystem, the same store works seamlessly across React, Vue, Solid, Preact, and Svelte — no framework-specific SDK required.
Prerequisites
Section titled “Prerequisites”Before you begin, make sure you have:
- A Croct account with an application set up
- Your application ID (found in the Croct dashboard)
- Slots configured with the content structure you want to personalize
Installation
Section titled “Installation”Install the croct-nanostores package along with @croct/plug and the connector for your framework:
npm install croct-nanostores @croct/plug @nanostores/reactnpm install croct-nanostores @croct/plug @nanostores/vuenpm install croct-nanostores @croct/plug @nanostores/solidnpm install croct-nanostores @croct/plug @nanostores/preactnpm install croct-nanostores @croct/plugCreating your first atom
Section titled “Creating your first atom”Use croctContent to create a reactive atom for a slot. You provide the slot ID and a fallback content object that matches the slot’s schema:
import { croctContent } from 'croct-nanostores';
export const bannerContent = croctContent('home-banner@1', { title: 'Welcome to our store', subtitle: 'Check out our latest products', ctaLabel: 'Shop now', ctaLink: '/products',});The atom immediately holds the fallback content, then fetches personalized content from Croct in the background. When the fetch completes, the atom updates and any subscribed component re-renders automatically.
Croct’s API is optimized for low-latency content delivery, so in most cases the personalized content will be available before your app finishes rendering. Your users see the right experience from the start, with no visible flash of fallback content.
Using the atom in your framework
Section titled “Using the atom in your framework”Once you have an atom, subscribe to it using your framework’s Nanostores connector:
import { useStore } from '@nanostores/react';import { bannerContent } from '../stores/banner';
export function Banner() { const banner = useStore(bannerContent);
return ( <section> <h1>{banner.content.title}</h1> <p>{banner.content.subtitle}</p> <a href={banner.content.ctaLink}> {banner.content.ctaLabel} </a> </section> );}<template> <section> <h1>{{ banner.content.title }}</h1> <p>{{ banner.content.subtitle }}</p> <a :href="banner.content.ctaLink"> {{ banner.content.ctaLabel }} </a> </section></template>
<script setup>import { useStore } from '@nanostores/vue';import { bannerContent } from '../stores/banner';
const banner = useStore(bannerContent);</script>import { useStore } from '@nanostores/solid';import { bannerContent } from '../stores/banner';
export function Banner() { const banner = useStore(bannerContent);
return ( <section> <h1>{banner().content.title}</h1> <p>{banner().content.subtitle}</p> <a href={banner().content.ctaLink}> {banner().content.ctaLabel} </a> </section> );}import { useStore } from '@nanostores/preact';import { bannerContent } from '../stores/banner';
export function Banner() { const banner = useStore(bannerContent);
return ( <section> <h1>{banner.content.title}</h1> <p>{banner.content.subtitle}</p> <a href={banner.content.ctaLink}> {banner.content.ctaLabel} </a> </section> );}<script>import { bannerContent } from '../stores/banner';</script>
<section> <h1>{$bannerContent.content.title}</h1> <p>{$bannerContent.content.subtitle}</p> <a href={$bannerContent.content.ctaLink}> {$bannerContent.content.ctaLabel} </a></section>Type safety
Section titled “Type safety”Croct Nanostores fully supports the type system from the Croct SDK. When you use the Croct CLI to generate type declarations for your slots, those types automatically flow through to your atoms and components.
Generating types
Section titled “Generating types”Run the Croct CLI to generate a type declaration file for your application’s slots:
npx croct typegenThis produces a .d.ts file that augments the @croct/plug/slot module with your slot schemas. For example, a slot named home-banner with version 1 generates types like:
type HomeBannerV1 = { title: string; subtitle: string; ctaLabel: string; ctaLink: string;};
declare module '@croct/plug/slot' { export interface VersionedSlotMap { 'home-banner': { latest: HomeBannerV1; '1': HomeBannerV1; }; }}How types flow through
Section titled “How types flow through”Once the type declaration file is in place, TypeScript validates your code end-to-end:
- Slot ID validation —
croctContent('home-banner@1', ...)checks that'home-banner'is a known slot and'1'is a valid version. - Fallback content validation — The fallback object you pass must match the slot’s content schema. Missing or extra properties produce a type error.
- Component-level inference — The
contentfield on the atom’s state is typed to the slot’s schema, so your components get full autocomplete and type checking.
// ✅ TypeScript validates the fallback matches the slot schemaconst banner = croctContent('home-banner@1', { title: 'Default title', subtitle: 'Default subtitle', ctaLabel: 'Learn more', ctaLink: '/about',});
// ❌ Type error — 'unknown-slot' is not a valid slot IDconst broken = croctContent('unknown-slot@1', { title: 'Oops' });Next steps
Section titled “Next steps”- Learn about the content rendering lifecycle — how atoms manage state, handle errors, persist content, and auto-refresh on user behavior changes.
- See the full API reference for all options and types.
- Check out the live demo to see multi-framework rendering in action.