# How to Detect and Handle Smooth Scroll Libraries Like Lenis or Locomotive Scroll in Next.js

> Learn to detect and handle smooth scroll libraries like Lenis or Locomotive Scroll in Next.js. Master client-side React hooks for seamless integration and prevent memory leaks with proper cleanup.

- Repository: [JCodesMore/ai-website-cloner-template](https://github.com/JCodesMore/ai-website-cloner-template)
- Tags: how-to-guide
- Published: 2026-07-03

---

**Detect smooth scroll libraries by scanning for CSS classes (`.lenis`, `.locomotive-scroll`), script sources, and initialization patterns, then implement them in your Next.js clone using client-side React hooks with proper cleanup to prevent memory leaks.**

When reverse-engineering websites with the [JCodesMore/ai-website-cloner-template](https://github.com/JCodesMore/ai-website-cloner-template), accurately reproducing the scroll experience requires identifying and implementing smooth scroll libraries like Lenis or Locomotive Scroll. These libraries hijack native scrolling to create inertia-based animations, but they leave distinct fingerprints in the DOM and network requests that you can detect programmatically. This guide covers the detection logic defined in the template's inspection workflows and the exact implementation steps for Next.js App Router projects.

## Detecting Smooth Scroll Libraries

The template's inspection process, documented in [`docs/research/INSPECTION_GUIDE.md`](https://github.com/JCodesMore/ai-website-cloner-template/blob/main/docs/research/INSPECTION_GUIDE.md) and the clone workflows, identifies smooth scroll libraries through specific DOM signatures and script patterns.

### Lenis Detection Signatures

Lenis typically reveals itself through:

- **CSS class** `.lenis` on wrapper elements that enable the smooth-scroll engine
- **Script tags** importing [`lenis.js`](https://github.com/JCodesMore/ai-website-cloner-template/blob/main/lenis.js) or `@lenis/core` from CDNs or npm
- **JavaScript initialization** patterns like `new Lenis(...)` or `lenis.raf(time)`
- **Global stylesheet modifications** that hide native scrollbars via `overflow: hidden` on the `html` element

According to the template's [`.windsurf/workflows/clone-website.md`](https://github.com/JCodesMore/ai-website-cloner-template/blob/main/.windsurf/workflows/clone-website.md), these markers are checked during the visual inspection phase.

### Locomotive Scroll Detection Patterns

Locomotive Scroll uses different conventions:

- **Container class** `.locomotive-scroll` on the primary scroll wrapper
- **Data attributes** like `data-scroll-container` on the main content wrapper
- **Script sources** containing [`locomotive-scroll.min.js`](https://github.com/JCodesMore/ai-website-cloner-template/blob/main/locomotive-scroll.min.js) or the npm package `locomotive-scroll`
- **Constructor calls** instantiating `new LocomotiveScroll({ el: ..., smooth: true })`

The [`.opencode/commands/clone-website.md`](https://github.com/JCodesMore/ai-website-cloner-template/blob/main/.opencode/commands/clone-website.md) file explicitly lists these signatures as critical detection points for the cloning workflow.

### Automated Detection with Playwright

For automated inspection, use a headless browser script to scan for these patterns:

```typescript
// utils/detect-scroll.ts
import { Page } from 'playwright';

export async function detectSmoothScroll(page: Page) {
  const hasLenis = await page.$('.lenis');
  const hasLocomotive = await page.$('.locomotive-scroll');
  const scripts = await page.$$eval('script[src]', (els) => 
    els.map((e) => e.getAttribute('src'))
  );
  const usesLenis = scripts.some((src) => src?.includes('lenis'));
  const usesLocomotive = scripts.some((src) => src?.includes('locomotive-scroll'));

  return {
    lenis: !!hasLenis || usesLenis,
    locomotive: !!hasLocomotive || usesLocomotive,
  };
}

```

This detection logic mirrors the checklist in the template's inspection guide, ensuring you flag the correct library before generating code.

## Implementing Smooth Scroll in Next.js

Once detected, implementing these libraries in a Next.js App Router project requires client-side initialization to avoid server-side rendering mismatches.

### Installing Dependencies

Add the detected library to your [`package.json`](https://github.com/JCodesMore/ai-website-cloner-template/blob/main/package.json):

```json
{
  "dependencies": {
    "lenis": "^1.0.0",
    "locomotive-scroll": "^4.1.4"
  }
}

```

The template's clone command automatically injects these dependencies when the detection phase identifies a smooth scroll library.

### Lenis Implementation

Create a client-side wrapper in [`src/app/layout.tsx`](https://github.com/JCodesMore/ai-website-cloner-template/blob/main/src/app/layout.tsx) with the `.lenis` class:

```tsx
'use client';
import { useEffect } from 'react';
import Lenis from '@lenis/core';
import '@/app/globals.css';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  useEffect(() => {
    const lenis = new Lenis({
      duration: 1.2,
      easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
    });

    function raf(time: number) {
      lenis.raf(time);
      requestAnimationFrame(raf);
    }
    requestAnimationFrame(raf);

    return () => lenis.destroy();
  }, []);

  return (
    <html lang="en">
      <body className="lenis">{children}</body>
    </html>
  );
}

```

The `lenis.raf()` method drives the animation frame updates, while `lenis.destroy()` in the cleanup function prevents memory leaks during route transitions.

### Locomotive Scroll Setup

For Locomotive Scroll, import the required CSS and initialize with the container element:

```tsx
'use client';
import { useEffect } from 'react';
import LocomotiveScroll from 'locomotive-scroll';
import 'locomotive-scroll/dist/locomotive-scroll.css';
import '@/app/globals.css';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  useEffect(() => {
    const scroll = new LocomotiveScroll({
      el: document.querySelector('.locomotive-scroll') as HTMLElement,
      smooth: true,
    });

    return () => scroll.destroy();
  }, []);

  return (
    <html lang="en">
      <body className="locomotive-scroll">{children}</body>
    </html>
  );
}

```

The `el` parameter must reference an element with the `.locomotive-scroll` class, matching the detection pattern used during inspection.

### Cleanup and Performance

Both libraries attach global event listeners and animation frames. **Always implement cleanup routines**:

- **Lenis**: Call `lenis.destroy()` to remove event listeners and stop the raf loop
- **Locomotive Scroll**: Call `scroll.destroy()` to unbind scroll handlers and remove injected styles

Without cleanup, navigating between pages in your Next.js app creates duplicate scroll instances, causing jitter and memory leaks.

## Configuration Files and Global Styles

Update [`src/app/globals.css`](https://github.com/JCodesMore/ai-website-cloner-template/blob/main/src/app/globals.css) to match the original site's scroll behavior:

```css
/* Hide native scrollbars when using smooth scroll libraries */
html {
  overflow: hidden;
  height: 100%;
}

body {
  height: 100%;
  overflow-y: auto;
  overflow-x: hidden;
}

```

The template references [`src/app/globals.css`](https://github.com/JCodesMore/ai-website-cloner-template/blob/main/src/app/globals.css) as the location for global scroll-related styles, ensuring visual consistency with the source website.

## Summary

- **Detection** relies on CSS classes (`.lenis`, `.locomotive-scroll`), script sources, and initialization patterns found in the template's inspection workflows
- **Implementation** requires wrapper elements with specific classes and client-side initialization using `'use client'` directives in Next.js
- **Cleanup** is mandatory—both libraries require `destroy()` methods to prevent memory leaks during route transitions
- **Global styles** in [`src/app/globals.css`](https://github.com/JCodesMore/ai-website-cloner-template/blob/main/src/app/globals.css) must account for hidden scrollbars and container heights to match the original experience
- **Reference files** include [`.windsurf/workflows/clone-website.md`](https://github.com/JCodesMore/ai-website-cloner-template/blob/main/.windsurf/workflows/clone-website.md), [`.opencode/commands/clone-website.md`](https://github.com/JCodesMore/ai-website-cloner-template/blob/main/.opencode/commands/clone-website.md), and [`docs/research/INSPECTION_GUIDE.md`](https://github.com/JCodesMore/ai-website-cloner-template/blob/main/docs/research/INSPECTION_GUIDE.md) for the complete detection logic

## Frequently Asked Questions

### How do I programmatically detect if a website uses Lenis or Locomotive Scroll?

Automate detection by scanning for CSS classes (`.lenis` or `.locomotive-scroll`), checking script `src` attributes for filenames containing "lenis" or "locomotive-scroll", and searching the JavaScript bundle for constructor patterns like `new Lenis()` or `new LocomotiveScroll()`. The template's Playwright-based detection script in [`utils/detect-scroll.ts`](https://github.com/JCodesMore/ai-website-cloner-template/blob/main/utils/detect-scroll.ts) combines these checks into a single boolean return for each library.

### Why does my Locomotive Scroll implementation break in Next.js?

Locomotive Scroll requires a DOM element reference for its `el` parameter, which doesn't exist during server-side rendering. You must mark the component as `'use client'` and initialize the library inside a `useEffect` hook. Additionally, failing to import the library's CSS ([`locomotive-scroll/dist/locomotive-scroll.css`](https://github.com/JCodesMore/ai-website-cloner-template/blob/main/locomotive-scroll/dist/locomotive-scroll.css)) or missing the `.locomotive-scroll` class on your container element will cause the scroll calculations to fail.

### What's the difference between Lenis and Locomotive Scroll detection?

Lenis typically uses the `.lenis` class on the body or a wrapper element and relies on `requestAnimationFrame` loops via `lenis.raf()`. Locomotive Scroll uses `.locomotive-scroll` classes combined with `data-scroll-container` attributes and requires a specific DOM element reference during initialization. The template's inspection guide in [`docs/research/INSPECTION_GUIDE.md`](https://github.com/JCodesMore/ai-website-cloner-template/blob/main/docs/research/INSPECTION_GUIDE.md) treats these as separate detection paths because their implementation code differs significantly.

### Where should I initialize smooth scroll libraries in a Next.js App Router application?

Initialize smooth scroll libraries in [`src/app/layout.tsx`](https://github.com/JCodesMore/ai-website-cloner-template/blob/main/src/app/layout.tsx) wrapped in a `useEffect` hook with a `'use client'` directive at the top of the file. This ensures the code runs only in the browser after hydration. Place the initialization logic in the root layout to maintain smooth scrolling across all routes, and always return a cleanup function that calls the library's `destroy()` method to handle route transitions properly.