Lessons from building MERN ERPs
Production status, a separate .NET PDF service, user-based S3 organization, and the boundaries of my ERP work.
In standard server-rendered websites, navigating to a new page naturally resets the window scroll position to the top. However, in single-page applications and Next.js App Router applications equipped with virtual smooth scrollers (like Lenis), client-side soft navigations frequently preserve the previous page's scroll offset or produce awkward delayed jumps.
The core issue is that virtual scrollers intercept native wheel and touch events, managing their own internal virtual coordinate state while translating the DOM or clamping native scroll. When Next.js executes a client transition via usePathname() or <Link>, the browser's native scroll restoration does not update Lenis's internal virtual accumulator. As a result, the user lands mid-page on the incoming route.
import { useEffect } from "react";
import { usePathname } from "next/navigation";
import { useLenis } from "lenis/react";
import { ScrollTrigger } from "gsap/ScrollTrigger";
export function RouteScrollReset() {
const pathname = usePathname();
const lenis = useLenis();
useEffect(() => {
// Immediate virtual scroll reset
if (lenis) {
lenis.scrollTo(0, { immediate: true });
} else {
window.scrollTo(0, 0);
}
// Re-calculate ScrollTrigger coordinate offsets for new route content
const timer = setTimeout(() => {
ScrollTrigger.refresh();
}, 100);
return () => clearTimeout(timer);
}, [pathname, lenis]);
return null;
}
Passing { immediate: true } to lenis.scrollTo(0) bypasses interpolation tweening, zeroing the scroll accumulator on the exact tick of route unmount. This avoids a disorienting animated scroll back to the top that distracts the user during page entry.
Furthermore, because new route DOM dimensions may not be fully painted immediately upon route trigger, a small delay (100ms) before invoking ScrollTrigger.refresh() gives the App Router layout pass enough time to settle before GSAP measures marker start/end trigger coordinates.
Production status, a separate .NET PDF service, user-based S3 organization, and the boundaries of my ERP work.
JSON templates, browser rendering, and remaining-space pagination in my client-side PDF generator.