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_EDITION = "main",
    ALOKAI_CMS_SPACE,
    ALOKAI_CMS_DELIVERY_TOKEN,
    ALOKAI_CMS_PREVIEW_TOKEN,
    } = process.env;
    export const config = {
    configuration: {
    baseUrl: ALOKAI_CMS_BASE_URL,
    edition: ALOKAI_CMS_EDITION,
    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_EDITION=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_EDITIONNoEdition 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