Building a Magical Particles Background in Next.js

2025-10-206 min read
#Next.js#UI#Frontend
Building a Magical Particles Background in Next.js

Introduction

Animated particle backgrounds are a simple way to make a landing page feel more alive without distracting from the main content. In this article, we will build a reusable particles component for a Next.js app that you can drop behind any section.

Goals

  • Keep the implementation small and framework friendly.
  • Render particles on a lightweight canvas element.
  • Expose simple props for quantity and styling so you can reuse it across pages.

Setting up the component

First, create a file app/_components/particles.tsx. The component will render a <canvas> that fills its parent and draw small circles that slowly move around.

import { useEffect, useRef } from "react";

type ParticlesProps = {
  quantity?: number;
  className?: string;
};

export function Particles({ quantity = 120, className }: ParticlesProps) {
  const canvasRef = useRef<HTMLCanvasElement | null>(null);

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;

    const ctx = canvas.getContext("2d");
    if (!ctx) return;

    let frameId: number;
    const particles: { x: number; y: number; vx: number; vy: number }[] = [];

    const resize = () => {
      canvas.width = canvas.offsetWidth;
      canvas.height = canvas.offsetHeight;
    };

    window.addEventListener("resize", resize);
    resize();

    for (let i = 0; i < quantity; i++) {
      particles.push({
        x: Math.random() * canvas.width,
        y: Math.random() * canvas.height,
        vx: (Math.random() - 0.5) * 0.5,
        vy: (Math.random() - 0.5) * 0.5,
      });
    }

    const render = () => {
      if (!canvas) return;
      ctx.clearRect(0, 0, canvas.width, canvas.height);

      ctx.fillStyle = "rgba(255,255,255,0.8)";

      for (const p of particles) {
        p.x += p.vx;
        p.y += p.vy;

        if (p.x < 0 || p.x > canvas.width) p.vx *= -1;
        if (p.y < 0 || p.y > canvas.height) p.vy *= -1;

        ctx.beginPath();
        ctx.arc(p.x, p.y, 1.4, 0, Math.PI * 2);
        ctx.fill();
      }

      frameId = requestAnimationFrame(render);
    };

    render();

    return () => {
      window.removeEventListener("resize", resize);
      cancelAnimationFrame(frameId);
    };
  }, [quantity]);

  return <canvas ref={canvasRef} className={className} />;
}

Using the particles in a page

With the component in place, you can place it behind any layout. In a Next.js app router page, you can do something like this:

import { Particles } from "@/app/_components/particles";

export default function Hero() {
  return (
    <section className="relative min-h-screen bg-black text-white overflow-hidden">
      <Particles className="absolute inset-0 -z-10" quantity={200} />
      <div className="relative z-10 mx-auto flex max-w-3xl flex-col items-start justify-center px-4 py-20">
        <h1 className="text-4xl font-bold md:text-5xl">
          Minimal particle background for your Next.js pages
        </h1>
        <p className="mt-4 text-neutral-300">
          Drop this component behind any section to add subtle motion without heavy
          dependencies or complex configuration.
        </p>
      </div>
    </section>
  );
}

Performance considerations

Because we are using a single canvas and a small number of tiny particles, the impact on performance is minimal. Avoid very large particle counts, and prefer soft, subtle animation instead of a dense field of shapes that draws attention away from your content.

Conclusion

You now have a self contained particles component that can be reused across pages and layouts. From here, you can extend it with color variations, different shapes, or interactions that respond to mouse movement or scroll position, while keeping the core idea simple.