D
P
0

Web Animation

Lenis in the <head> Threw 'document.body is null' and Blocked Render — Move It to the Footer

June 22, 2026·3 min read
Lenis in the <head> Threw 'document.body is null' and Blocked Render — Move It to the Footer

I added Lenis for smooth scroll to a cinematic site I built, and instead of buttery scrolling I got two problems at once. First, the console immediately threw:

TypeError: document.body is null

Second, the page felt slower to appear — there was a render delay that wasn't there before. Before I could even enjoy the animation, the site got heavier up front.

Why this happens

Both problems came from the same single mistake: the Lenis library was enqueued inside the document <head>.

When a script sits in the <head>, it executes before the browser finishes parsing the <body>. At that point, document.body is still null. Lenis needs the body element on init (it attaches listeners and reads dimensions off the body), so when init runs too early:

const lenis = new Lenis(); // document.body is still null here

…it throws TypeError: document.body is null outright. Smooth scroll never even activates.

And the second problem is a consequence of the same placement: a script in the <head> is render-blocking. The browser has to download and execute it before it continues rendering the document. So on top of the error, Lenis in the head was also holding back first paint.

The fix for both problems is the exact same single move: shift the Lenis library enqueue and its init to the document footer.

In a WordPress environment, that means setting the $in_footer argument to true:

wp_enqueue_script(
    'lenis',
    'https://unpkg.com/lenis@1/dist/lenis.min.js',
    [],
    null,
    true // ← in_footer: run after the body exists
);

Or in plain HTML, just put the <script> right before </body>:

    <!-- ... page content ... -->
    <script src="/js/lenis.min.js"></script>
    <script src="/js/lenis-init.js"></script>
  </body>

Once Lenis loads in the footer, it runs after the <body> exists — document.body is no longer null, init succeeds, the TypeError is gone. And because it's no longer in the head, it stops blocking render. One move, both problems solved.

Extra hardening from the same project

While fixing this, there were a few other things I had to handle so Lenis wouldn't fight the rest of the site:

  • Scrollers inside modals/overlays. The nav body and search overlay have their own scroll. Lenis would "swallow" their scrolling. Add data-lenis-prevent so those elements keep native scrolling:
<div class="search-overlay__body" data-lenis-prevent>
  <!-- content that scrolls natively -->
</div>
  • Pages with a 3D customizer/canvas. On pages with a 3D canvas using OrbitControls, Lenis's wheel handling conflicts with the camera controls. The fix: don't enable Lenis at all on those pages, and let OrbitControls fully own the mouse wheel.

  • prefers-reduced-motion. Respect the accessibility preference — skip Lenis when the user asks for reduced motion:

const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (!reduceMotion) {
  const lenis = new Lenis();
  // ... raf loop
}

The lesson

A smooth-scroll library that touches document.body must load after the body exists — enqueue it in the footer, never the head. And remember that a head script is render-blocking no matter what it contains, so moving it to the footer cures both the document.body is null error and the render delay in a single move. The rest — data-lenis-prevent on inner scrollers, skipping Lenis on 3D canvas pages, and honoring prefers-reduced-motion — is the hardening that makes smooth scroll feel right without breaking other interactions.