Skip to content
Deckard
Esc
navigateopen⌘Jpreview
On this page

Troubleshooting

The errors a deck actually prints, what each one means, and what to change. Search this page for the message you got.

Two commands answer most of it before you read further.

deckard validate   # the slides changed
deckard doctor     # a deck that used to work stopped

validate loads the deck and checks its slides and its theme. doctor checks the app around it: Node, whether @thebuilder/deckard-core resolves, whether the stylesheet is imported, whether the four route files still re-export their adapter, and whether deck/deck.ts loads. Both exit 1 on a problem, and doctor keeps going so you see all of them at once.

A slide does not appear

Symptom. You added deck/slides/roadmap.slide.tsx and the deck still has the old slide count. No error.

Cause. Either the file is not in the glob, or the dev server is serving stale modules.

Discovery matches one pattern:

import.meta.glob("./slides/**/*.slide.tsx", { eager: true })

A file is picked up only if it is under deck/slides/ and its name ends in .slide.tsx. roadmap.tsx is not, and neither is a file next to slides.tsx instead of inside slides/. Nothing warns about a file that misses.

Fix. Check the name and the directory, then restart next dev. Adding or deleting a matched file while the dev server runs leaves the page routes serving the modules they had at boot, so a correctly named file can still be missing until you restart.

deckard validate prints the count it actually resolved:

deck   6 slides: 4 inline, 2 discovered

If the discovered number is not what you expect, it is the glob. If it is right and the browser disagrees, it is the dev server.

Discovered slide “…” has no default export

Symptom.

Discovered slide "slides/roadmap.slide.tsx" has no default export. A slide module exports its component as the default, plus optional meta and notes.

Cause. The module exports the component by name.

Fix. Export it as the default. meta and notes stay named:

export const meta: SlideMeta = { slug: "roadmap", title: "Roadmap" }
export const notes = "Skip the middle row if time is short."

export default function RoadmapSlide() {
  return <OpenContentSlide title="Roadmap">{/* … */}</OpenContentSlide>
}

Discovered slide “…” is an async module

Symptom.

Discovered slide "slides/api.slide.tsx" is an async module, so the glob handed back a promise instead of its exports. Import it in the deck array with slideFromModule, or drop the dependency that uses top-level await.

Cause. The module, or something it imports, uses top-level await or WebAssembly. The eager glob then returns a promise instead of the exports.

CodeBlock is the usual one. Shiki loads a WebAssembly regex engine, which is why it ships from @thebuilder/deckard-core/code-block and not from the components barrel. A discovered slide that imports it becomes async.

Fix. Take that slide out of discovery and wire it into the array by hand:

import * as apiSlide from "@/deck/slides/api.slide"
import { slideFromModule } from "@thebuilder/deckard-core/slide-from-module"

export const slides: SlideDefinition[] = [
  ...discovered,
  slideFromModule(apiSlide, "deck/slides/api.slide.tsx"),
]

The file keeps its name and its place, and stops going through the glob.

Duplicate or invalid slug

All four fail the deck at load, so they surface from deckard validate, from doctor, and from the build.

Two slides on one slug.

Slides 4 and 5 both use the slug "keyboard". Slide ids must be unique.

Rename one. The numbers are 1-based positions in the deck.

A numeric slug.

Slide 3 has the numeric slug "2024". A numeric slug adds nothing over /slides/3 and breaks when slides move, so use letters or drop the slug.

Characters that are not safe in a path.

Slide 3 has the slug "Q4 Results", which is not safe in a URL path. Use lowercase letters, digits, and hyphens.

q4-results works.

An empty slug.

Slide 3 has an empty slug. Give it a value or drop the slug so the slide is served at /slides/3.

A slide overflows the canvas

Symptom, in the browser. An amber ring around the canvas under next dev, and a console line:

Slide content runs 794px below the canvas and is clipped.

Symptom, in CI.

/slides/roadmap  slide content runs 794px below the canvas
1 of 6 slides overflow the canvas and are clipped. Trim the content, or wrap the part that has to scroll in SlideScrollArea.

Cause. The canvas is a fixed box and it clips silently. The three measured regions are the slide frame, the header, and the footer, so the header or the footer in that line means the chrome grew, not the body.

Fix. Cut a sentence. That is the right answer most of the time and the slide is usually better for it. When the content genuinely has to be there, wrap the part that can scroll:

<SlideScrollArea label="Full configuration" maxHeight={360}>
  <ConfigTable />
</SlideScrollArea>

A stepped slide is measured fully revealed, because hidden steps keep their space in the layout. The canvas has the rest.

Slide “…” rendered no [data-slide-frame] element

Slide roadmap rendered no [data-slide-frame] element. Check that the route still renders SlideShell.

The overflow check found the page but not the canvas. app/slides/[id]/page.tsx has stopped re-exporting createSlideRoute. deckard doctor reports the same thing as a routes failure.

Theme validation fails

deckard validate reads the stylesheet the deck actually renders, so these name either @thebuilder/deckard-themes/<id> or your ejected deck/theme/theme.css.

The class is not in the stylesheet.

Theme "custom" sets className "custom-theme", but deck/theme/theme.css has no rule for .custom-theme. The canvas carries that class, so none of the theme reaches the slide.

The canvas gets the class from the theme object. If the stylesheet has no rule for it, the deck renders in app defaults. Check for a typo on one side or the other.

A dark mode the stylesheet never paints.

Theme "custom" lists "dark" in colorModes, but deck/theme/theme.css has no dark block. The toggle switches to a mode the theme never paints.

A dark block the theme does not list.

deck/theme/theme.css has a dark block, but theme "custom" does not list "dark" in colorModes.

Add "dark" to colorModes, or delete the block.

A token defined only in dark.

deck/theme/theme.css defines --slide-surface-border only in the dark block. Light mode renders without it.

Every token needs a light value. Dark overrides a subset.

The default names a mode the theme does not carry. These come from the theme object rather than the stylesheet:

Slide theme "custom" defaults to "system" but only supports dark. Support both color modes or pick one as the default.

The presenter window does not open

Symptom. P or the popout button does nothing at all. No error, no console message.

Cause. The browser blocked the popup. window.open returned nothing and the code does not report it.

Fix. Allow popups for the deck’s origin, then press P again.

Also check the other two reasons the key does nothing: P is ignored while focus is in an input, textarea, select, or contenteditable, and while any modifier is held. And a slide loaded with ?presenterPreview=1 is read-only by design, so its keys do not fire.

The presenter window does not sync

Symptom. The presenter window opens and stays on Waiting for connection from the slideshow tab.

Cause. The two windows talk over a BroadcastChannel named slideshow-presenter-sync, and that channel is same-origin and same-browser. Nothing routes through a server.

So it will not connect when:

  • The deck and the presenter window are on different origins, including localhost:3000 against 127.0.0.1:3000, and http against https.
  • They are in different browsers, or one is in a private window.
  • The browser has no BroadcastChannel. The runtime checks for it and carries on without sync rather than failing.

Fix. Open the presenter window from the deck itself, with P or the popout button, so it inherits the origin. It reads Connected via BroadcastChannel. once the first message lands.

A presenter window opened by pasting the URL into a different browser will never connect, and that is the design rather than a fault.

PDF export cannot find Chromium

Symptom.

Failed to launch Chromium. Run: pnpm exec playwright install chromium

Cause. The Playwright package is installed in the deck, but the browser binary is a separate download.

Fix. Run the line the message printed. It names your deck’s package manager, so an npm deck is told npm exec -- playwright install chromium and a bun deck bunx playwright install chromium.

This hits check-overflow, screenshots, contact-sheet, and export pdf. It never hits validate, doctor, init, add, or eject, which drive no browser.

A capture command cannot find playwright or pdf-lib

Symptom.

playwright does not resolve from this deck. The capture commands load it from the deck's own node_modules. Install it: pnpm add -D playwright

Cause. The CLI does not ship the browser or the PDF writer. deckard init writes both into the deck’s devDependencies; a deck added to an existing app by hand, or one that removed them, has neither.

Fix. Run the line the message printed, then the Chromium download above. deckard doctor lists both packages under tooling.

The port is taken

Something is already serving http://localhost:3410. Stop it, or pass --port=<free port>.

The capture commands refuse to run against a server they did not start, because it might be serving a different build. Stop it or move.

No screenshots to compose

No screenshots at /path/to/deck/out/screenshots. Run: pnpm run deck:screenshots

contact-sheet reads the manifest that screenshots writes and builds nothing itself.

The sitemap has no slides

No slide routes in the sitemap. Check that app/sitemap.ts still lists /slides/* entries.

The capture commands read the slide list from the deck’s own /sitemap.xml. An app that dropped or rewrote that route breaks all three.

The registry cannot be reached

Symptom.

The registry at https://deckard.thebuilder.dk/r/metrics.json did not answer.

Cause. The URL in components.json points at the Deckard docs site, which serves every registry item at /r/{name}.json. Either the site is unreachable or the URL was changed.

Fix. Check the network, or point deckard add block somewhere else with --registry <url>. The URL carries a {name} placeholder.

A different message, no registry configured at all:

No @deckard registry in components.json and no --registry flag.
Add one: { "registries": { "@deckard": "https://deckard.thebuilder.dk/r/{name}.json" } }

The blocks are yours as source either way. deckard add block is for taking an update or undoing an edit, so a deck that never needs one never needs the registry. Blocks and the registry has the details.

Styles are missing

Symptom. The deck renders as unstyled text, or the layout is right and every colour is wrong.

Cause and fix. deckard doctor names it:

FAIL styles  app/globals.css never imports @import "@thebuilder/deckard-core/styles.css"
             Add @import "@thebuilder/deckard-core/styles.css"; to app/globals.css. It carries the --slide-* tokens and registers the runtime's own Tailwind source.

The check is a literal substring test, so the import has to read exactly @import "@thebuilder/deckard-core/styles.css".

If the import is there and the deck is still unstyled, the theme class is the next thing to check. deckard validate prints the stylesheet it read and the token counts:

theme  meridian (.meridian-theme): light and dark, default system
       57 tokens in the light block, 25 dark overrides, from @thebuilder/deckard-themes/meridian

Zero tokens, or a stylesheet path you did not expect, is the answer.

Other messages

A slide that throws under next dev renders an inline card reading Slide <id> threw while rendering, and the rest of the deck keeps working. In a production build the same throw is fatal to the route and Next serves its own error page, so fix it before you build.

deckard <command> --help is an error rather than help. --help lives at the top level only.

Unknown flag --help. This command takes: --light, --port, --skip-build.

deckard eject theme run twice refuses, because there is a local theme it will not overwrite. It is a one-way door.

node fails in doctor:

Next 16 needs node 20.9.0 or newer.

Requirements has the full version table.

Was this page helpful?