hakimel/reveal.js · error · Error

Async markdown parsing is not supported here because Reveal

Error message

Async markdown parsing is not supported here because Reveal markdown parsing is synchronous.

What it means

ensureParsedMarkdown (react/src/utils/markdown.ts:110) guards the contract that Reveal markdown parsing stays synchronous. It is called on the result of markedInstance.parse(...) inside createSlideHtml (:125). When marked is configured in async mode (async:true, async walkTokens, or an async extension), parse returns a Promise<string>, which this guard rejects. Reveal's slide pipeline cannot await, so async results are treated as programmer error rather than deferred.

Source

Thrown at react/src/utils/markdown.ts:113

			const text = token.tokens ? this.parser.parseInline(token.tokens) : token.text || '';
			return `<li class="fragment">${text}</li>`;
		};
	}

	const markedInstance = new Marked();
	markedInstance.use({ renderer, ...markedOptions });

	if (smartypants) {
		markedInstance.use(markedSmartypants());
	}

	return markedInstance;
}

function ensureParsedMarkdown(result: string | Promise<string>) {
	if (typeof result === 'string') return result;

	throw new Error(
		'Async markdown parsing is not supported here because Reveal markdown parsing is synchronous.'
	);
}

function createSlideHtml(markdown: string, markedInstance: Marked, notesSeparator: string) {
	const notesMatch = markdown.split(new RegExp(notesSeparator, 'mgi'));
	let slideMarkdown = markdown;
	let notesHtml = '';

	if (notesMatch.length === 2) {
		slideMarkdown = notesMatch[0];
		notesHtml = `<aside class="notes">${ensureParsedMarkdown(
			markedInstance.parse(notesMatch[1].trim())
		)}</aside>`;
	}

	return `${ensureParsedMarkdown(markedInstance.parse(slideMarkdown))}${notesHtml}`;
}

View on GitHub (pinned to a3b9406956)

Solutions

  1. Remove async:true and any async walkTokens/tokenizer from the marked configuration passed to the Markdown component — parsing must stay synchronous.
  2. If you need async data (e.g. fetched snippets or highlighted code), resolve it before passing the final string as the markdown/children, so parse only does synchronous work.
  3. Pin/verify the marked version, since the sync-vs-async default has changed across major versions.
  4. If a third-party marked extension forces async, replace it with a synchronous equivalent or preprocess its output upstream.

Example fix

// before — forces async parsing
<Markdown markedOptions={{ async: true, walkTokens: async (t) => { await fetchMeta(t); } }} />

// after — keep parsing synchronous; resolve async data beforehand
const slides = await resolveAllSnippets(rawMarkdown);
return <Markdown>{slides}</Markdown>;
Defensive patterns

Strategy: validation

Validate before calling

function assertSyncMarkedOptions(markedOptions) {
  if (markedOptions?.async === true) {
    throw new Error('Reveal markdown requires synchronous marked; remove async:true.');
  }
  if (typeof markedOptions?.walkTokens?.constructor?.name === 'AsyncFunction') {
    throw new Error('Reveal markdown requires a synchronous walkTokens.');
  }
  return markedOptions;
}

Type guard

function isSyncParseResult(result) {
  return typeof result === 'string';
}

Prevention

When it happens

Trigger: Passing markedOptions.async = true (or any object whose async flag is truthy) into the Markdown component's marked config; registering a marked extension with an async walkTokens or async tokenizer/lexer; upgrading marked and inheriting a default that flips parse to async; supplying a custom renderer that returns a Promise.

Common situations: Developers adding marked plugins (e.g. async syntax highlighters, remote-data extensions) that turn parsing async; assuming marked.parse is always synchronous; copying marked options from a Node sample that used async:true for streaming.

Related errors


AI-assisted analysis of hakimel/reveal.js@a3b9406956 (2026-08-12). Data as JSON: /api/errors/65c492540618dcef. Report an issue: GitHub.