Use Lenis with Next.js 13

Tutorial16 September 2023

Next.js 13 brought a lot of exciting new features, but perhaps one of the most significant changes is the introduction of the App Router, replacing the conventional Pages Router used in previous versions.

In this article, we'll explore how to leverage this powerful new router alongside Lenis by Studio Freight, a lightweight smooth-scrolling library which uses native scroll to enhance your Next.js projects. We'll be using TypeScript for this tutorial, but fear not, the process is adaptable for vanilla Javascript as well, by simply removing the types and using .jsx instead of .tsx file extensions.

Installing the necessary packages

Before diving into the implementation details, make sure you have a Next.js 13 project set up with the new /app directory structure. With that done, let's start by installing the required npm packages for Lenis and its companion library, Tempus:

npm i @studio-freight/lenis @studio-freight/tempus

Creating the Lenis custom component

Next, we'll create a custom component that encapsulates the Lenis functionality. In your project's /src/components/ directory, create a file named Lenis.tsx. The contents of this file should resemble the following:

import React, { useEffect, useLayoutEffect, useRef } from 'react';
import Tempus from '@studio-freight/tempus';
import Lenis from '@studio-freight/lenis';
import { usePathname, useSearchParams } from 'next/navigation';

export default function SmoothScroller() {
const lenis = useRef<Lenis | null>(null);
const pathname = usePathname();
const searchParams = useSearchParams();

useEffect(() => {
if (lenis.current) lenis.current.scrollTo(0, { immediate: true });
}, [pathname, searchParams, lenis]);

useLayoutEffect(() => {
  lenis.current = new Lenis({
  smoothWheel: true,
  // Customize other instance settings here
  });

  const resize = setInterval(() => {
    lenis.current!.resize();
  }, 150);

  function onFrame(time: number) {
    lenis.current!.raf(time);
  }

  const unsubscribe = Tempus.add(onFrame);

  return () => {
    unsubscribe();
    clearInterval(resize);
    lenis.current!.destroy();
    lenis.current = null;
  };

}, []);

return null;

}

With this code we define a React component named SmoothScroller. It imports the necessary libraries and hooks, including React, Lenis, Tempus, usePathname, and useSearchParams. Inside the component, it initializes a lenis ref to store a reference to the Lenis instance.

The useEffect hook watches for changes in the pathname or searchParams (representing the URL). When a change is detected, it scrolls the page to the top using Lenis if an instance exists.

The useLayoutEffect hook runs when the component is mounted and performs the following tasks:

  • Initializes a new instance of Lenis with smooth wheel scrolling enabled.
  • Sets up an interval to handle resizing of the Lenis instance.
  • Defines an onFrame function to handle animation frames.
  • Subscribes to animation frames using Tempus and calls onFrame on each frame.
  • Returns a cleanup function to unsubscribe from animation frames, clear the resize interval, and destroy the Lenis instance when the component unmounts.

The component itself doesn't render any visible UI elements, so it returns null.

Feel free to adjust the Lenis instance settings to match your specific requirements. For a comprehensive list of settings, consult the Lenis documentation on their GitHub repository.

Importing and using the custom Lenis component

With the custom SmoothScroller component in place, you can now integrate it into your layout.tsx file as follows:

import React from "react";
import SmoothScroller from "@/components/Lenis"; // Adjust the import path as needed

export default function RootLayout({
  children,
}: {
  children: React.ReactNode,
}) {
  return (
    <html>
      <body>
        <SmoothScroller />
        {children}
      </body>
    </html>
  );
}

By adding the SmoothScroller component to your layout.tsx, you initialize the Lenis instance on the client-side and ensure its seamless application across server-side rendered (SSR) pages.

In conclusion

In this article, we've seen how to maintain clean and modular code by encapsulating Lenis functionality within a client-side component. Enjoy your enhanced Next.js 13 project with the App Router and Lenis integration!