leptos-rs/leptos · error

queueMicrotask not available

Error message

queueMicrotask not available

What it means

DomRenderer::queue_microtask reads window.queueMicrotask via reflection and panics if the property is absent. The library expects a standard browser environment; throwing here flags a runtime where microtask scheduling is unavailable.

Source

Thrown at tachys/src/renderer/dom.rs:53

pub type CssStyleDeclaration = web_sys::CssStyleDeclaration;
pub type TemplateElement = web_sys::HtmlTemplateElement;

/// A microtask is a short function which will run after the current task has
/// completed its work and when there is no other code waiting to be run before
/// control of the execution context is returned to the browser's event loop.
///
/// Microtasks are especially useful for libraries and frameworks that need
/// to perform final cleanup or other just-before-rendering tasks.
///
/// [MDN queueMicrotask](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask)
pub fn queue_microtask(task: impl FnOnce() + 'static) {
    use js_sys::{Function, Reflect};

    let task = Closure::once_into_js(task);
    let window = window();
    let queue_microtask =
        Reflect::get(&window, &JsValue::from_str("queueMicrotask"))
            .expect("queueMicrotask not available");
    let queue_microtask = queue_microtask.unchecked_into::<Function>();
    _ = queue_microtask.call1(&JsValue::UNDEFINED, &task);
}

fn queue(fun: Box<dyn FnOnce()>) {
    use std::cell::{Cell, RefCell};

    thread_local! {
        static PENDING: Cell<bool> = const { Cell::new(false) };
        static QUEUE: RefCell<Vec<Box<dyn FnOnce()>>> = RefCell::new(Vec::new());
    }

    QUEUE.with_borrow_mut(|q| q.push(fun));
    if !PENDING.replace(true) {
        queue_microtask(|| {
            let tasks = QUEUE.take();
            for task in tasks {
                task();

View on GitHub (pinned to 32d20f6c9d)

Solutions

  1. Run in a real browser or polyfill queueMicrotask (e.g. via jsdom in tests or a core-js/microtask shim)
  2. For SSR, ensure the DOM renderer is not invoked server-side (use the appropriate renderer for the platform)
  3. Use Promise.resolve().then-based polyfill assigned onto globalThis/window before app boot
  4. Upgrade the webview/runtime to one supporting queueMicrotask

Example fix

// before
// Node test without polyfill -> panics
// after
// polyfill.mjs loaded first
globalThis.queueMicrotask ??= (fn) => Promise.resolve().then(fn);
Defensive patterns

Strategy: fallback

Validate before calling

let has_microtask = js_sys::Reflect::get(&web_sys::window().unwrap(), &wasm_bindgen::JsValue::from_str("queueMicrotask"))
    .map(|v| !v.is_undefined()).unwrap_or(false);

Try / catch

// install a polyfill fallback before app boot:
if !has_microtask() {
    js_sys::eval("globalThis.queueMicrotask = (f) => Promise.resolve().then(f);").unwrap();
}

Prevention

When it happens

Trigger: Running the WASM DOM renderer in a non-browser JS environment (Node without jsdom/polyfills, minimal JS runtimes, service workers without window, outdated embedded webviews) where window.queueMicrotask is undefined.

Common situations: SSR/hydration tests running leptos DOM code under Node; custom wasm test harnesses; old WebKit-based webviews lacking queueMicrotask; bundler configs shimming window incompletely.

Related errors


AI-assisted analysis of leptos-rs/leptos@32d20f6c9d (2026-09-01). Data as JSON: /api/errors/415b455550806210. Report an issue: GitHub.