npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@flotiq/nextjs-live-preview

v0.9.0

Published

Support for live preview in Next.JS framework in flotiq-api-sdk

Readme

@flotiq/nextjs-live-preview

A comprehensive Next.js integration for Flotiq CMS that provides real-time live preview based on the new notification + data endpoints. This package allows content editors to see changes while editing content in Flotiq CMS.

Features

  • 🔄 Real-time content updates via WS /ws/notify/:docKey
  • 📦 Collaboration-aware reads via GET /data/:docKey?hydration=<n>
  • 🧭 Identifying-field matching (for example slug-based routes)
  • 👥 Collaborator-aware field indicators with LivePreviewBox
  • 📱 Flotiq iframe support for seamless CMS integration
  • Throttled updates to prevent excessive re-renders
  • 🎨 Customizable UI components for editor indicators

Installation

npm install @flotiq/nextjs-live-preview

Requirements

  • React: 19.0.0 or higher
  • Next.js: 15.0.0 or higher
  • Flotiq API SDK: 0.6.0 or higher

How It Works

  1. Session Setup: live preview route stores required spaceId, contentType, objectId, apiKey, plus optional userId and identifyingFields in cookie.
  2. Request Matching: middleware checks SDK content requests against session data (direct object id match or identifying-fields filter match).
  3. Data Read: matched requests are served from GET /data/:docKey with hydration.
  4. Notify Refresh: client hook subscribes to WS /ws/notify/:docKey and triggers router.refresh() on notifications.

Quick Start

1. Configure the Middleware

Add the live preview middleware to your Flotiq API configuration:

import { Flotiq } from "@flotiq/flotiq-api-sdk";
import { createNextLivePreviewMiddleware } from "@flotiq/nextjs-live-preview";

const api = new Flotiq({
  middleware: [createNextLivePreviewMiddleware()],
});

Function createNextLivePreviewMiddleware supports provided options:

  • flotiqApiKey - optional. It uses FLOTIQ_API_KEY env by default. It can be used to override Flotiq API key
  • singletonTypes - optional. Array of singleton content type definitions that should resolve preview data even when the request does not include an object id or identifying-field filters

2. Create Live Preview API Route

Create the API route that handles live preview mode toggling in app/api/flotiq/live-preview/route.ts file.

import { NextRequest } from "next/server";
import { redirect } from "next/navigation";
import { livePreview } from "@flotiq/nextjs-live-preview/server";

export async function GET(req: NextRequest) {
  const redirectPath = req.nextUrl.searchParams.get("redirect") || "/";

  /**
   * Validate the request.
   *
   * To validate the request you can use the `Client Authorization Key` passed to the
   * live preview URL from **Live Preview** plugin as `editor_key` URL param.
   * Make sure to define it in your application's environment variables and pass the same
   * `Client Authorization Key` to the plugin settings.
   * Without validating this key, everybody could see your latest data.
   *
   * Example validation:
   *
   * const clientAuthKey = req.nextUrl.searchParams.get('editor_key');
   * if (clientAuthKey !== process.env.FLOTIQ_CLIENT_AUTH_KEY) {
   *   return new Response('Unauthorized', { status: 401 });
   * }
   */

  const livePreviewState = await livePreview();
  const newLivePreview = req.nextUrl.searchParams.has("live-preview")
    ? req.nextUrl.searchParams.get("live-preview") === "true"
    : !livePreviewState.isEnabled;

  if (newLivePreview) {
    livePreviewState.enable(req);
  } else {
    livePreviewState.disable();
  }

  redirect(redirectPath);
}

Live preview route expects these query parameters:

  • Required: spaceId, apiKey, objectId, contentType
  • Optional: userId
  • Optional: identifyingFields (used for list-request matching) as JSON string, for example: identifyingFields={"slug":"post-slug"}

3. Add Live Preview Status Component

This component keeps router refresh in sync with preview notifications and renders the default live preview banner. If you don't want to use predefined banner, you can use useLivePreviewStatus hook and create your own component (more info).

import { livePreview } from "@flotiq/nextjs-live-preview/server";
import { LivePreviewStatus } from "@flotiq/nextjs-live-preview/client";

export default async function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const { isEnabled } = await livePreview();

  return (
    <html>
      <body>
        <main>
          {isEnabled && (
            <LivePreviewStatus editorKey={process.env.FLOTIQ_EDITOR_KEY} />
          )}
          {children}
        </main>
      </body>
    </html>
  );
}

Preview Transport

  • GET /data/:docKey?hydration=<n> is used by middleware to fetch collaboration-aware object state.
  • WS /ws/notify/:docKey?apiKey=<key>&hydration=<n> is used by client hook to receive best-effort refresh notifications (hydration is currently sent as 0 by default).
  • :docKey is built as space/contentType/objectId.
  • Client applications do not sync Yjs documents anymore.
  • Hydration and relation composition are handled by preview data server, not by this package.
  • Matching logic for list requests supports identifying fields from session data (identifyingFields).
  • Matching logic for list requests can also be forced for configured singleton content types via singletonTypes.

Advanced Usage

Live Preview Boxes for Field Editing

Wrap your content fields with LivePreviewBox to show visual editing indicators and enable field-specific interactions:

import { LivePreviewBox } from "@flotiq/nextjs-live-preview/server";

export default async function BlogPost({
  params,
}: {
  params: { slug: string };
}) {
  const post = await flotiqApi.ContentBlogpostAPI.get(params.slug, {
    hydrate: 1,
  });

  return (
    <article>
      <LivePreviewBox data={post} fieldName="title">
        <h1>{post.title}</h1>
      </LivePreviewBox>
    </article>
  );
}

Custom Live Preview Hook

If you don't want to use LivePreviewStatus, you can use useLivePreviewStatus directly. This hook refreshes the router after notify websocket events.

Example code with custom banner:

"use client";

import { useLivePreviewStatus } from "@flotiq/nextjs-live-preview/client";

export function CustomLivePreviewIndicator() {
  const { isConnected, updatedTime, contentType, objectId } =
    useLivePreviewStatus();

  if (!isConnected) return null;

  return (
    <div className="live-preview-custom">
      <span>
        Editing {contentType}#{objectId}
      </span>
      {updatedTime && <span>Last update: {updatedTime}</span>}
    </div>
  );
}

Configuration Options

Middleware Options

interface NextLivePreviewMiddlewareOptions {
  /**
   * Flotiq API key for authorization
   * @default process.env.FLOTIQ_API_KEY
   */
  flotiqApiKey?: string;

  /**
   * Singleton content types that should use preview data for list requests
   * even when the request does not include identifying filters.
   * @default []
   */
  singletonTypes?: string[];
}

LivePreviewBox Props

interface LivePreviewBoxProps {
  /** Field content to wrap */
  children: React.ReactNode;
  /** Field name in the content object */
  fieldName: string;
  /** Content object data with id, internal.contentType and collaborator metadata */
  data: {
    id: string;
    __collaborators?: unknown[]; // Injected by Flotiq Collab Gateway v2
    internal: {
      contentType: string;
    };
  };
  /** Scroll offset for auto-focus (default: 0) */
  scrollTopOffset?: number;
  /** Use light color theme for editor indicators (default: false) */
  lightColors?: boolean;
  /** Additional CSS class name */
  className?: string;
  /** Outline offset in pixels (default: 6) */
  outlineOffset?: number;
}

LivePreviewStatus Props

interface LivePreviewStatusProps {
  /** Client Authorization key */
  editorKey?: string;
  /** Additional CSS class name */
  className?: string;
  /**
   * Maximum time (in ms) to wait for a page refresh to finish.
   * If Next.js doesn't complete the refresh in time, it is treated as timed out.
   * Set to `-1` to disable timeout-based resolution.
   * Enabled by default due to [known Next.js App Router refresh issues](#known-nextjs-app-router-refresh-issues).
   * @default 500
   */
  refreshTimeout?: number;
  /**
   * When enabled, the timeout duration automatically adjusts based on the average
   * duration of previous successful refreshes (plus a 50% buffer).
   * Requires `refreshTimeout` to provide the initial baseline sample.
   * Enabled by default due to [known Next.js App Router refresh issues](#known-nextjs-app-router-refresh-issues).
   * @default true
   */
  enableAdaptiveTimeout?: boolean;
  /**
   * After this many consecutive refresh timeouts, live preview falls back to a hard browser reload.
   * Set to `0` or `-1` to disable this fallback.
   * Enabled by default due to [known Next.js App Router refresh issues](#known-nextjs-app-router-refresh-issues).
   * @default 5
   */
  hardReloadAfterConsecutiveTimeouts?: number;
}

The useLivePreviewStatus hook accepts the same refresh timing options.

useLivePreviewStatus({
  refreshTimeout: 500,
  enableAdaptiveTimeout: true,
  hardReloadAfterConsecutiveTimeouts: 5,
});

Migration from <0.8.0 to >=0.8.0

Version 0.8.0 switches collaboration to Flotiq Collaboration Gateway v2 and updates the outward-facing live preview API around LivePreviewBox and endpoint configuration.

What Changed

  • Collaboration protocols switched to Flotiq Collaboration Gateway v2
  • LivePreviewBox no longer reads collaborator state from global in-memory room state.
  • LivePreviewBox no longer accepts objectId and ctdName props. These are now derived from the data prop.
  • LivePreviewBox supports outlineOffset for customizing outline spacing.
  • Server-side preview reads now use WEBSOCKET_ENDPOINT or fall back to NEXT_PUBLIC_WEBSOCKET_ENDPOINT.
  • Client-side live preview notifications use NEXT_PUBLIC_WEBSOCKET_ENDPOINT.
  • Deprecated clearCache server helper was removed.
  • Deprecated connectionMode middleware option was removed.
  • added option singletonTypes to specify singleton content that is requested without identifiers

Required Changes

In your codebase

  1. Update every LivePreviewBox usage to pass the full content object as data instead of objectId and ctdName.
  2. Make sure the object passed to data comes from preview-aware SDK reads, such as get(...) or list(..., { limit: 1 }). Flotiq Collaboration Gateway v2 injects collaborator metadata into that object automatically.
  3. Remove any usage of clearCache or connectionMode.
  4. Set NEXT_PUBLIC_WEBSOCKET_ENDPOINT for the browser and, if needed, WEBSOCKET_ENDPOINT for server-side preview reads. Default variables connect to public Flotiq Collab Gateway (wss://collab-gateway.flotiq.com).
  5. If both env vars are set, point them to the same Flotiq Collaboration Gateway v2 instance.
  6. Each data type that is designed to have single object only (singletons) should be added in singletonTypes option of createNextLivePreviewMiddleware. This will enable live preview for one-off pages and data like global settings, brand, contact info etc.

In Flotiq Dashboard

  1. Go to Flotiq Dasboard and update Live Preview Plugin to v0.8.1 or higher

LivePreviewBox Migration

Before 0.8.0:

<LivePreviewBox
  objectId={post.id}
  ctdName="blogpost"
  fieldName="title"
>
  <h1>{post.title}</h1>
</LivePreviewBox>

From 0.8.0 onward:

<LivePreviewBox data={post} fieldName="title">
  <h1>{post.title}</h1>
</LivePreviewBox>

LivePreviewBox now derives objectId from data.id and ctdName from data.internal.contentType. Use the object returned by preview-aware SDK get(...) or list(..., { limit: 1 }) reads, and the gateway will inject the collaborator metadata needed for editor indicators.

Logging

Live preview provides configurable log levels for debugging. Both client and server loggers default to info.

Accepted values: silly, debug, info, warn, error.

The default level info produces no output under normal operation. Set to warn to see only refresh timeouts and connection issues, debug to also see fallback decisions, or silly to trace every lifecycle event (WebSocket open/close, refresh start/complete, middleware matching).

| Environment variable | Runtime | |---|---| | NEXT_PUBLIC_FLOTIQ_LIVE_PREVIEW_LOG_LEVEL | Client (browser) | | FLOTIQ_LIVE_PREVIEW_LOG_LEVEL | Server (Node.js) |

Troubleshooting

  • Ensure cookies are enabled in your browser
  • Check that the API route is correctly implemented
  • Verify the Client Authorization Key matches in both Flotiq and application
  • Verify preview endpoint config for both runtimes
  • Server-side data reads use WEBSOCKET_ENDPOINT (or fallback NEXT_PUBLIC_WEBSOCKET_ENDPOINT)
  • Client-side notifications use NEXT_PUBLIC_WEBSOCKET_ENDPOINT
  • If both are set, point them to the same preview server exposing /data and /ws/notify

Known Next.js App Router Refresh Issues

Some Next.js App Router applications can fail to apply router.refresh() reliably during live preview, especially when loading.tsx or other Suspense boundaries are present. Two practical failure modes have been observed:

  • router.refresh() does not settle, so later live preview refreshes never start.
  • the browser receives fresh RSC payload data, but the App Router keeps stale UI until a full browser reload.

To reduce the impact of these upstream issues, live preview enables three mitigations by default:

<LivePreviewStatus
  editorKey={process.env.FLOTIQ_EDITOR_KEY}
  refreshTimeout={500}
  enableAdaptiveTimeout
  hardReloadAfterConsecutiveTimeouts={5}
/>
  • refreshTimeout={500} sets the initial upper bound for a soft refresh to settle.
  • enableAdaptiveTimeout learns from successful refreshes and uses average duration + 50% for the next timeout.
  • hardReloadAfterConsecutiveTimeouts={5} forces a browser reload after five consecutive soft-refresh timeouts.

This is a workaround for known upstream Next.js limitations and bugs around App Router refresh completion:

  • https://github.com/vercel/next.js/discussions/58520
  • https://github.com/vercel/next.js/discussions/88066
  • https://github.com/vercel/next.js/discussions/49810
  • https://github.com/vercel/next.js/issues/86151

If you don't like/don't need the defaults as workaround, you can disable them by setting the following options:

  • refreshTimeout={-1} disables timeout-based resolution.
  • hardReloadAfterConsecutiveTimeouts={0} and hardReloadAfterConsecutiveTimeouts={-1} both disable the hard reload fallback.