Skip to main content
nextjs-slides leverages Next.js App Router’s dynamic routes to create URL-based navigation for your slide deck. Each slide has its own URL (e.g. /slides/1, /slides/2), enabling shareable links and browser history.

Route Structure

A typical slide deck setup uses this file structure:
app/slides/
  layout.tsx          ← SlideDeck provider
  page.tsx            ← Redirects to /slides/1
  [page]/page.tsx     ← Dynamic route for slides
  slides.tsx          ← Slide definitions

getSlide

getSlide is a helper that resolves the current slide from route params:
app/slides/[page]/page.tsx
import { getSlide } from 'nextjs-slides';
import { slides } from '../slides';

export default async function SlidePage({
  params,
}: {
  params: Promise<{ page: string }>;
}) {
  return getSlide(await params, slides);
}

How It Works

getSlide extracts the page number from params, converts it to a zero-based index, and returns the corresponding slide:
get-slide.tsx
export function getSlide(
  params: { page: string },
  slides: React.ReactNode[]
): React.ReactNode {
  const index = Number(params.page) - 1;

  if (isNaN(index) || index < 0 || index >= slides.length) {
    notFound();
  }

  return slides[index];
}
  • 1-based URLs/slides/1 maps to slides[0]
  • Validation — Returns Next.js 404 if the page number is invalid or out of range
  • Type safety — Accepts a typed params object
getSlide uses Next.js notFound() to trigger a 404 response for invalid slide numbers.

generateSlideParams

generateSlideParams generates static params for all slides, enabling static site generation:
app/slides/[page]/page.tsx
import { generateSlideParams, getSlide } from 'nextjs-slides';
import { slides } from '../slides';

export const generateStaticParams = () => generateSlideParams(slides);

export default async function SlidePage({
  params,
}: {
  params: Promise<{ page: string }>;
}) {
  return getSlide(await params, slides);
}

How It Works

generateSlideParams maps each slide to a params object with a 1-based page number:
get-slide.tsx
export function generateSlideParams(slides: React.ReactNode[]) {
  return slides.map((_, i) => ({ page: String(i + 1) }));
}
For a deck with 12 slides, this generates:
[
  { page: '1' },
  { page: '2' },
  { page: '3' },
  // ...
  { page: '12' },
]
Next.js uses this to pre-render all slide routes at build time.
Use generateStaticParams for production builds to pre-render all slides. This eliminates server round trips and ensures instant navigation.

Index Route

Create an index route that redirects to the first slide:
app/slides/page.tsx
import { redirect } from 'next/navigation';

export default function SlidesPage() {
  redirect('/slides/1');
}
This ensures visitors to /slides are automatically redirected to /slides/1.

Custom Base Path

To use a different URL prefix, configure both the SlideDeck and your routes:

1. Update SlideDeck

app/slides-alt/layout.tsx
import { SlideDeck } from 'nextjs-slides';
import { slides } from './slides';

export default function AltSlidesLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <SlideDeck slides={slides} basePath="/slides-alt">
      {children}
    </SlideDeck>
  );
}

2. Create Routes

app/slides-alt/page.tsx
import { redirect } from 'next/navigation';

export default function AltSlidesPage() {
  redirect('/slides-alt/1');
}
app/slides-alt/[page]/page.tsx
import { getSlide, generateSlideParams } from 'nextjs-slides';
import { slides } from '../slides';

export const generateStaticParams = () => generateSlideParams(slides);

export default async function AltSlidePage({
  params,
}: {
  params: Promise<{ page: string }>;
}) {
  return getSlide(await params, slides);
}
Now your deck is available at /slides-alt/1, /slides-alt/2, etc.

Breakout Pages

Pages inside the slides folder but outside the [page] route render without deck navigation:
app/slides/
  layout.tsx          ← SlideDeck provider
  [page]/page.tsx     ← Slide routes with navigation
  demo/page.tsx       ← Breakout page (no progress dots/counter)
Breakout pages inherit the slide deck’s layout but don’t match the slide route pattern, so they render without keyboard navigation or progress indicators. Useful for live demos or interactive examples.
app/slides/demo/page.tsx
export default function DemoPage() {
  return (
    <div className="flex h-dvh items-center justify-center">
      <h1>Live Demo</h1>
      {/* Interactive content */}
    </div>
  );
}
Link to breakout pages from a slide using SlideLink:
import { Slide, SlideTitle, SlideLink } from 'nextjs-slides';

<Slide key="demo-link">
  <SlideTitle>Try It Yourself</SlideTitle>
  <SlideLink href="/slides/demo">Open Demo</SlideLink>
</Slide>
Breakout pages are detected by the SlideDeck route pattern. If the pathname doesn’t match /slides/[number], it’s treated as a breakout page.

How SlideDeck Detects Routes

SlideDeck uses a regex pattern to determine if the current route is a slide:
slide-deck.tsx
const slideRoutePattern = useMemo(
  () =>
    new RegExp(`^${basePath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}/(\\d+)$`),
  [basePath]
);
const isSlideRoute = slideRoutePattern.test(pathname);
  • Slide routes/slides/1, /slides/12isSlideRoute = true
  • Breakout pages/slides/demo, /slides/liveisSlideRoute = false

Route Prefetching

SlideDeck prefetches adjacent slides for instant navigation:
slide-deck.tsx
useEffect(() => {
  if (!isSlideRoute) return;
  if (current > 0) router.prefetch(`${basePath}/${current}`);
  if (current < total - 1) router.prefetch(`${basePath}/${current + 2}`);
}, [basePath, current, isSlideRoute, router, total]);
This ensures the previous and next slides are loaded in the background, making keyboard navigation feel instant.

Build docs developers (and LLMs) love