Next.js App Router
Use the React lightbox in the Next.js App Router: fetch slides in a Server Component, pass them down, and skip the dynamic import.
The published bundle starts with "use client". You can import Lightbox from a file in the App Router tree and Next.js treats it as a Client Component.
Your gallery still needs to be a Client Component, because it holds state:
"use client";
import { useRef, useState } from "react";
import { Lightbox, type Slide } from "liftbox";
export function Gallery({ slides }: { slides: Slide[] }) {
const [index, setIndex] = useState(-1);
const thumbnails = useRef<(HTMLButtonElement | null)[]>([]);
// same grid as in the quick start
}import { Gallery } from "./gallery";
import { getPhotos } from "@/lib/photos";
export default async function Page() {
const slides = await getPhotos();
return <Gallery slides={slides} />;
}Slides are plain objects, so a Server Component can fetch them and pass them down. title and description accept React nodes, which also serialize as long as they are plain elements.
The stylesheet
Import it once in app/layout.tsx, or in the client component:
import "liftbox/styles.css";No ssr: false needed
The lightbox does not touch window or document while rendering. The dialog is portalled only after hydration, so dynamic(() => import(...), { ssr: false }) is not needed and only delays the code. See SSR for what happens on the server.
next/image thumbnails
Use next/image for the grid if you like. Forward its ref the same way, since getThumbnail only needs an element:
<button
ref={(el) => {
thumbnails.current[i] = el;
}}
onClick={() => setIndex(i)}
>
<Image src={slide.thumbnail!} alt="" width={200} height={133} />
</button>For the slides themselves, pass plain URLs. If you want the Next.js image optimizer, build src and srcSet from its loader URLs.