Skip to content

Installation

This guide covers integrating Alokai CMS into an existing Alokai storefront (Next.js). The integration consists of three parts: the middleware connector, the SDK module, and the React rendering layer.

Prerequisites

  • An Alokai storefront (Next.js)
  • Access to an Alokai CMS instance (see Dev Docs → Local Development)
  • @alokai/cms-api package available in your registry

Setup

  1. Install the API package

    Terminal window
    yarn add @alokai/cms-api
  2. Add the middleware integration

    Create apps/storefront-middleware/sf-modules/cms-alokai-cms/config.ts:

    import type { ApiClientExtension, Integration } from '@alokai/connect/middleware';
    import type { MiddlewareConfig } from '@alokai/cms-api';
    const {
    ALOKAI_CMS_BASE_URL = 'http://localhost:8787',
    ALOKAI_CMS_ENVIRONMENT = 'main',
    ALOKAI_CMS_SPACE,
    ALOKAI_CMS_DELIVERY_TOKEN,
    ALOKAI_CMS_PREVIEW_TOKEN,
    } = process.env;
    export const config = {
    configuration: {
    baseUrl: ALOKAI_CMS_BASE_URL,
    environment: ALOKAI_CMS_ENVIRONMENT,
    space: ALOKAI_CMS_SPACE,
    deliveryToken: ALOKAI_CMS_DELIVERY_TOKEN!,
    previewToken: ALOKAI_CMS_PREVIEW_TOKEN,
    },
    location: '@alokai/cms-api/server',
    } satisfies Integration<MiddlewareConfig>;

    Then register it in middleware.config.ts:

    import { config as cmsConfig } from './sf-modules/cms-alokai-cms/config';
    export default {
    integrations: {
    // ... other integrations
    alokai-cms: cmsConfig,
    },
    };
  3. Add the SDK module

    Create apps/storefront-unified-nextjs/sdk/modules/unified-alokai-cms.ts:

    import { defineSdkModule } from '@vue-storefront/next';
    import type { UnifiedAlokaiCmsEndpoints } from 'storefront-middleware/types';
    export const unifiedAlokaiCms = defineSdkModule(({ buildModule, config, getRequestHeaders, middlewareModule }) =>
    buildModule(middlewareModule<UnifiedAlokaiCmsEndpoints>, {
    apiUrl: `${config.apiUrl}/alokai-cms/unified`,
    ssrApiUrl: `${config.ssrApiUrl}/alokai-cms/unified`,
    defaultRequestConfig: {
    headers: getRequestHeaders(),
    },
    }),
    );

    Register it in your SDK config:

    import { unifiedAlokaiCms } from './modules/unified-alokai-cms';
    export const sdk = createSdk(options, ({ buildModule }) => ({
    // ... other modules
    unifiedAlokaiCms: buildModule(unifiedAlokaiCms),
    }));
  4. Set environment variables

    Add to your middleware .env:

    Terminal window
    ALOKAI_CMS_BASE_URL=https://your-cms.workers.dev
    ALOKAI_CMS_SPACE=your-space-id
    ALOKAI_CMS_ENVIRONMENT=main
    ALOKAI_CMS_DELIVERY_TOKEN=myorg_tok_...
    ALOKAI_CMS_PREVIEW_TOKEN=myorg_tok_...
  5. Add the rendering component

    Create components/cms/page/render-cms-content.tsx and map your CMS component api_id values to React components:

    import type { AgnosticCmsComponent } from '@vsf-enterprise/cms-components-utils';
    import Hero from '@/components/cms/page/hero';
    import Banner from '@/components/cms/page/banner';
    // ... import your other components
    const components: Record<string, ComponentType<any>> = {
    Hero,
    Banner,
    // ... register components by their CMS api_id
    };
    export default function RenderCmsContent({ item }: { item: AgnosticCmsComponent | AgnosticCmsComponent[] }) {
    if (Array.isArray(item)) {
    return <>{item.map((c, i) => <RenderCmsContent key={c.id} item={c} />)}</>;
    }
    const { component, ...props } = item;
    const Component = components[component];
    if (!Component) return null;
    return <Component {...props} />;
    }
  6. Add the connectCmsPage wrapper and live preview

    Create sf-modules/cms-alokai-cms/components/connect-cms-page.tsx — this connects CMS pages to Next.js and enables live preview:

    import type { ComponentType } from 'react';
    import { getSdk } from '@/sdk';
    import LivePreviewWrapper from '@/sf-modules/cms-alokai-cms/components/live-preview-wrapper';
    const componentsByPath: Record<string, ComponentType<any>> = {};
    const appLocaleToCmsLocale: Record<string, string> = { de: 'de-DE', en: 'en-US' };
    export default function connectCmsPage<TProps>(
    PageComponent: ComponentType<TProps>,
    config: { getCmsPagePath: (props: TProps) => Promise<string> | string },
    ) {
    async function CmsPage(props: any) {
    const sdk = await getSdk();
    const path = await config.getCmsPagePath(props);
    const params = await props.params;
    const locale = appLocaleToCmsLocale[params.locale];
    const searchParams = await props.searchParams;
    const isPreview = searchParams?.alokai_cms_preview === 'true';
    componentsByPath[path] = PageComponent;
    const page = await sdk.unifiedAlokaiCms
    .getPage({ locale, path, ...(isPreview && { preview: true }) })
    .catch(() => null);
    async function rerender(pageData: Record<string, unknown>) {
    'use server';
    const Component = componentsByPath[path];
    return <Component {...props} page={pageData} />;
    }
    if (isPreview) {
    return (
    <>
    <div id="ssr-content">
    <PageComponent {...props} page={page} />
    </div>
    <LivePreviewWrapper rerender={rerender} />
    </>
    );
    }
    return <PageComponent {...props} page={page} />;
    }
    return CmsPage;
    }

    Create sf-modules/cms-alokai-cms/components/live-preview-wrapper.tsx:

    'use client';
    import { debounce } from 'lodash-es';
    import type { ReactNode } from 'react';
    import { useCallback, useEffect, useState } from 'react';
    export default function LivePreviewWrapper({
    rerender,
    }: {
    rerender: (pageData: Record<string, unknown>) => Promise<ReactNode>;
    }) {
    const [previewContent, setPreviewContent] = useState<ReactNode>(null);
    const debouncedRerender = useCallback(
    debounce(async (pageData: Record<string, unknown>) => {
    setPreviewContent(await rerender(pageData));
    }, 300),
    [rerender],
    );
    useEffect(() => {
    function handleMessage(event: MessageEvent) {
    if (event.data?.type === 'alokai-cms:preview') {
    if (event.data.page) debouncedRerender(event.data.page);
    }
    }
    window.addEventListener('message', handleMessage);
    return () => window.removeEventListener('message', handleMessage);
    }, [debouncedRerender]);
    useEffect(() => {
    const ssr = document.getElementById('ssr-content');
    if (previewContent && ssr) ssr.style.display = 'none';
    }, [previewContent]);
    return <>{previewContent}</>;
    }
  7. Create a CMS page

    Use connectCmsPage in your route:

    // app/[locale]/(cms)/[[...slug]]/page.tsx
    import { notFound } from 'next/navigation';
    import connectCmsPage from '@/sf-modules/cms-alokai-cms/components/connect-cms-page';
    import RenderCmsContent from '@/sf-modules/cms-alokai-cms/components/render-cms-content';
    export default connectCmsPage(
    ({ page }) => {
    if (!page) return notFound();
    return (
    <>
    <RenderCmsContent item={page.componentsAboveFold ?? []} />
    <RenderCmsContent item={page.componentsBelowFold ?? []} />
    </>
    );
    },
    {
    async getCmsPagePath(props) {
    const params = await props.params;
    return `/${params.slug?.join('/') ?? ''}`;
    },
    },
    );

Environment variables reference

VariableRequiredDescription
ALOKAI_CMS_BASE_URLYesURL of your Alokai CMS worker
ALOKAI_CMS_SPACEYesSpace ID from the CMS dashboard
ALOKAI_CMS_ENVIRONMENTNoEnvironment name (default: main)
ALOKAI_CMS_DELIVERY_TOKENYesToken with delivery permission — serves published content
ALOKAI_CMS_PREVIEW_TOKENNoToken with preview permission — serves draft content + live preview