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:
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:
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:
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
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.
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:
const slideRoutePattern = useMemo(
() =>
new RegExp(`^${basePath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}/(\\d+)$`),
[basePath]
);
const isSlideRoute = slideRoutePattern.test(pathname);
- Slide routes —
/slides/1, /slides/12 → isSlideRoute = true
- Breakout pages —
/slides/demo, /slides/live → isSlideRoute = false
Route Prefetching
SlideDeck prefetches adjacent slides for instant navigation:
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.