Write speaker notes in markdown and display them during your presentation. Notes are parsed from a markdown file where each section corresponds to a slide.
Speaker notes use a simple markdown format: one section per slide, separated by --- on its own line. Sections are matched to slides by position:
- First section → Slide 1
- Second section → Slide 2
- Empty sections → No notes for that slide
Welcome everyone. This is the opening slide.
---
Talk about the base container here.
---
---
Slide 4 notes. Slide 3 had none.
Keep the number of sections in sync with your slides. If you have 12 slides, you need 12 sections (empty sections are fine).
Parsing Notes
Use parseSpeakerNotes() to convert the markdown file into an array that matches your slides:
import fs from 'fs';
import path from 'path';
import { SlideDeck, parseSpeakerNotes } from 'nextjs-slides';
import { slides } from './slides';
const notes = parseSpeakerNotes(
fs.readFileSync(path.join(process.cwd(), 'app/slides/notes.md'), 'utf-8')
);
export default function SlidesLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<SlideDeck
slides={slides}
speakerNotes={notes}
syncEndpoint="/api/nxs-sync"
>
{children}
</SlideDeck>
);
}
The function returns (string | null)[] where:
notes[0] corresponds to slide 1
notes[1] corresponds to slide 2
null entries indicate no notes for that slide
Stripping Leading Titles
If your notes file starts with a document title (a single markdown heading), use stripLeadingTitle: true to prevent it from being treated as slide 1:
# My Presentation Notes
This is slide 1 content.
---
This is slide 2 content.
const notes = parseSpeakerNotes(
fs.readFileSync(path.join(process.cwd(), 'app/slides/notes.md'), 'utf-8'),
{ stripLeadingTitle: true }
);
Without this option, ”# My Presentation Notes” would be treated as the notes for slide 1.
Section Separators
The --- separator must be on its own line:
First slide notes.
---
Second slide notes.
The parser splits on /^---$/m (a line containing only ---), so surrounding whitespace is trimmed but the separator needs its own line.
Demo Notes
Add extra sections after your last slide to create demo notes. These appear after the presentation ends and let you step through talking points for live demos:
...last slide notes
---
Open the counter demo. Show how useState drives the count.
---
Switch to the editor. Walk through adding a new slide.
During the presentation, the phone auto-syncs to the current slide. Once you navigate past the last slide, the header switches to “Demo 1 / 2” and you manually control the demo notes.
See Phone Sync for details on setting up the sync endpoint and notes viewer.
Implementation Details
The parseSpeakerNotes function is implemented as:
src/parse-speaker-notes.ts
export function parseSpeakerNotes(
markdown: string,
options?: { stripLeadingTitle?: boolean }
): (string | null)[] {
let sections = markdown.split(/^---$/m).map((section) => {
const trimmed = section.trim();
return trimmed.length > 0 ? trimmed : null;
});
if (
options?.stripLeadingTitle &&
sections[0] &&
/^#+\s+.+$/.test(sections[0].replace(/\n.*/s, ''))
) {
sections = sections.slice(1);
}
return sections;
}
It splits the markdown on --- separators, trims whitespace, and optionally removes a leading title section if the first line matches a markdown heading pattern (# Title).