【Next.js】Framer Motion(motion)の使い方|入場とスクロールをコード例で解説

公開日: 2026年9月12日 · 読了目安 13 分

Next.js の App Router で Framer Motion(motion/react)を使う手順をコード付きで解説。"use client" の置き場所、順番に出る入場、whileInView、useScroll の背景移動、よくあるビルドエラーの直し方までまとめます。

要点: motion を入れ、"use client" を付けた部品の中で "motion/react" から読み込みます。ページの残りはサーバーコンポーネントのままにして、動く部品だけをそこから呼び出します。

Next.js で Framer Motion を使うときの基本の形

Next.js の App Router で Motion(旧 Framer Motion)を入れたら、createContext のエラーが出た。page.tsx の先頭に "use client" を書いたら動いたが、本当にこれでよいのか。

そんな迷いからこの記事にたどり着いた人は多いはずです。答えはシンプルです。

ページ本体は、サーバーコンポーネント(サーバーで組み立てる部品)のままにします。

動かしたい部品だけを別ファイルに切り出します。そのファイルの先頭にだけ "use client"(この部品はブラウザで動かす、という印)を書きます。

ヒーローや CTA など、動きが必要な場所に絞って Motion を使います。

この記事で分かることは次のとおりです。

  • framer-motion と motion の違いと、どちらを入れるか

  • ブラウザで動かす印を置く場所と、ページ本体から呼び出す形

  • 見出し・本文・ボタンを順番に出す入場アニメーション

  • 画面に入ったときに一度だけ動かす方法

  • スクロールに合わせて背景だけを動かす方法

  • よくあるビルドエラーと、動きを減らす設定への対応

Framer Motion の部品だけをクライアントにする理由

Motion のコンポーネントは、ブラウザで動く React の機能(状態やコンテキスト)を使います。そのため、サーバーで組み立てる部品の中では直接動きません。

ここまでは多くの人が知っているところです。問題は、エラーを消すためにページ本体まで丸ごとブラウザ向けにしてしまうことです。

そうすると、そのページで使う子コンポーネントもすべてブラウザ向けの JavaScript に含まれます。データの取得やメタ情報の生成も、サーバー側の利点を失いやすくなります。

動く部品だけを切り出せば、ブラウザに送るコードは小さく保てます。レビューするときも「動きの責任はこのファイル」と範囲がはっきりします。

AI エージェントに修正を頼むときも、渡すファイルが少ないほど指示がぶれません。

なお、ふわっと出るだけの入場なら CSS の animation でも作れます。Motion を足すと手間に見合うのは、次のような場面です。

  • スクロール量に合わせて動かしたい

  • 画面に入った瞬間に一度だけ動かしたい

  • 複数の要素を順番に出したい

framer-motion と motion/react はどちらを入れるか

Framer Motion は現在「Motion」という名前で開発が続いています。新しいパッケージ名は motion で、React からは motion/react を import(読み込み)します。

従来の framer-motion パッケージも引き続き使えます。そのため、検索すると両方の書き方が出てきます。

新しく始めるなら、新しい名前のパッケージを入れて読み込み元もそろえるのがおすすめです。コピーしてきたコードが旧名から読み込んでいる場合は、旧名のパッケージをそのまま入れても動きます。

大事なのは、読み込み元をどちらか一方にそろえることです。

motion は内部で framer-motion を使っています。別々のバージョンを入れると重複してバンドルされたり、MotionConfig などの設定が共有されなかったりします。

bash
# 新しく始めるなら
npm install motion

# 既存コードが framer-motion を import しているなら
npm install framer-motion

動かすファイルだけに "use client" を書く

動く要素(コードの motion.h1 など)を使うファイルの先頭に、この1行を書きます。ページ本体からは、普通の部品として呼び出すだけです。

components/HeroTitle.tsx
"use client";
  
  import { motion } from "motion/react";
  
  export function HeroTitle({ children }: { children: React.ReactNode }) {
    return (
      <motion.h1
        initial={{ opacity: 0, y: 16 }}
        animate={{ opacity: 1, y: 0 }}
        transition={{ duration: 0.5, ease: [0.22, 1, 0.36, 1] }}
        className="text-4xl font-semibold tracking-tight md:text-6xl"
      >
        {children}
      </motion.h1>
    );
  }

ページ本体はサーバー側のまま動く部品を呼び出す

ページ本体には、ブラウザで動かす印を書きません。データの取得やページ情報(metadata)の定義はここに残し、動く部分だけを子の部品に任せます。

app/page.tsx
import { HeroTitle } from "@/components/HeroTitle";
  
  export default function Page() {
    return (
      <main className="mx-auto max-w-5xl px-6 py-24">
        <HeroTitle>Next.js で作る、動きのある LP</HeroTitle>
        <p className="mt-6 text-lg text-zinc-600">説明文はサーバー側で描画されます。</p>
      </main>
    );
  }

Framer Motion で入場アニメーションを順番に出す

見出しと本文とボタンを同じタイミングで出すと、動いているのに印象に残りません。目に留まる入場は、要素ごとに少しずつ遅らせた「段差」から生まれます。

Motion では、親の要素に variants を書きます。これは「隠れた状態」と「見えた状態」に名前を付けてまとめる設定です。

あわせて staggerChildren(子を何秒ずつずらして出すか)を指定すると、子が順番に表示されます。遅らせる間隔は 0.08〜0.16 秒が目安です。

イージングは最後にゆっくり止まる ease-out 寄りにすると、LP のテンポが崩れにくくなります。

components/HeroIntro.tsx
"use client";
  
  import { motion, type Variants } from "motion/react";
  
  const container: Variants = {
    hidden: {},
    show: { transition: { staggerChildren: 0.12, delayChildren: 0.1 } },
  };
  
  const item: Variants = {
    hidden: { opacity: 0, y: 16 },
    show: { opacity: 1, y: 0, transition: { duration: 0.5, ease: [0.22, 1, 0.36, 1] } },
  };
  
  export function HeroIntro() {
    return (
      <motion.div variants={container} initial="hidden" animate="show" className="space-y-6">
        <motion.h1 variants={item} className="text-5xl font-semibold">見出し</motion.h1>
        <motion.p variants={item} className="text-lg text-zinc-600">本文の説明</motion.p>
        <motion.a variants={item} href="/signup" className="inline-block rounded-full bg-black px-6 py-3 text-white">
          無料で始める
        </motion.a>
      </motion.div>
    );
  }

入場アニメーションでも画像は最初から見せておく

ヒーローの大きな画像を透明度 0 から出すと、ページで一番大きな要素の表示が遅れます。これは LCP(ページの主な内容が表示されるまでの時間)の悪化につながります。

画像は最初から見せておき、文字だけを動かすと安全です。画像も動かすなら、透明度を 0.6 程度から上げる形にします。

Framer Motion で画面に入ったときに一度だけ動かす

ページの途中にあるセクションは、読み込み時ではなく、スクロールして画面に入ったときに動かします。whileInView(画面に入ったら動かす指定)を使うと、要素が見えた瞬間にアニメーションが始まります。

一度表示したあとに同じ動きを繰り返さないようにするには、viewport の once を true にします。一度動いたあとは監視をやめるので、上に戻るたびに動きが繰り返されることもありません。

amount は「要素の何割が見えたら動かすか」を表します。

components/FadeInSection.tsx
"use client";
  
  import { motion } from "motion/react";
  
  export function FadeInSection({ children }: { children: React.ReactNode }) {
    return (
      <motion.section
        initial={{ opacity: 0, y: 24 }}
        whileInView={{ opacity: 1, y: 0 }}
        viewport={{ once: true, amount: 0.3 }}
        transition={{ duration: 0.6, ease: "easeOut" }}
      >
        {children}
      </motion.section>
    );
  }

Framer Motion でスクロールに合わせて背景を動かす

スクロール量に連動させたいときは、useScroll と useTransform を組み合わせます。

useScroll は、スクロール量を読み取る仕組みです。target に要素を渡すと、その要素が画面を通過する進み具合が 0〜1 の値で取れます。

その値を、useTransform という値を変換する仕組みで移動量に変えます。

動かすのは背景画像だけにして、移動量は数パーセントに抑えてください。文字まで追従させると読みにくくなります。

背景を少し大きめに置いておくと、動いたときに端の隙間が見えません。

components/ParallaxHero.tsx
"use client";
  
  import { useRef } from "react";
  import { motion, useScroll, useTransform } from "motion/react";
  
  export function ParallaxHero() {
    const ref = useRef<HTMLElement>(null);
    const { scrollYProgress } = useScroll({
      target: ref,
      offset: ["start start", "end start"],
    });
    const y = useTransform(scrollYProgress, [0, 1], ["0%", "15%"]);
  
    return (
      <section ref={ref} className="relative h-[80vh] overflow-hidden">
        <motion.div
          style={{ y, backgroundImage: "url(/hero.jpg)" }}
          className="absolute inset-x-0 -top-[10%] h-[120%] bg-cover bg-center"
        />
        <div className="relative z-10 flex h-full items-center justify-center">
          <h1 className="text-5xl font-semibold text-white">見出しは動かさない</h1>
        </div>
      </section>
    );
  }

Framer Motion を「動きを減らす」設定に対応させる

OS の設定で「視差効果を減らす」「動きを減らす」を選んでいる人がいます。CSS では prefers-reduced-motion で判定する設定です。

Motion では MotionConfig の reducedMotion="user" を使います。この設定に合わせて移動の動きを止め、透明度の変化だけを残せます。

レイアウトの近くに1つ置けば、その中の動く要素すべてに効きます。情報が動きに頼らないようにすることも、あわせて確認してください。

components/MotionProvider.tsx
"use client";
  
  import { MotionConfig } from "motion/react";
  
  export function MotionProvider({ children }: { children: React.ReactNode }) {
    return <MotionConfig reducedMotion="user">{children}</MotionConfig>;
  }

Framer Motion でよくある失敗とビルドエラーの直し方

Next.js と Motion の組み合わせでつまずく場面は、ほぼ決まっています。エラー文が出たら、まず次の4つを疑ってください。

createContext only works in Client Components などのエラーが出る

使っているバージョンによってエラー文は変わりますが、原因はどれも同じです。

動く部品のファイルに、ブラウザで動かす印("use client")がありません。ページ本体ではなく、動きのライブラリを読み込んでいるファイルの先頭に書きます。

Module not found: Can't resolve 'framer-motion' と出る

パッケージが入っていないか、読み込み元と入れたパッケージが食い違っています。

framer-motion を一度も入れていないのに、コードが framer-motion から読み込んでいる場合によく起きます。pnpm のように、直接入れたパッケージしか読み込めない構成でも起きます。

import を motion/react に直すか、framer-motion を入れてください。

ページ全体をブラウザで動く部品にしてしまう

エラーは消えますが、ページ全体がブラウザ向けのコードになります。ブラウザで動く部品では、ページ情報(metadata)を書き出すこともできません。

動く部品だけを切り出す形に戻してください。

全セクションに同じ強さの動きを付ける

すべてが動くと、どこが大事なのか伝わりません。強い動きはヒーローと最後の CTA に絞り、それ以外は薄いフェードで十分です。

完成した Framer Motion のセクションをコピーして使う方法

DesignLayer のコンポーネント一覧。左のカテゴリで用途ごとに絞り込める
DesignLayer のコンポーネント一覧。左のカテゴリで用途ごとに絞り込める

イージングや遅延を自分の手で細かく決めたいなら、この記事のコードを土台に組むのが確実です。

一方で、ページの雰囲気を先に決めたい場合もあります。既存の shadcn/ui のページにセクションを足したいときも同じです。

そうした場合は、動きが完成したセクションを載せる方が早く進みます。

DesignLayer は、人が組んだ React + Tailwind のモーション付きセクションを、ブラウザでライブプレビューできるカタログです。

閲覧は無料で、カードの登録もいりません。https://design-layer.com/ja/components

Copy for Cursor を使うと、統合の指示・ライブプレビューの URL・本体の TSX・npm 依存をまとめてコピーできます。無料サンプル10個を無料アカウントで回数制限なくコピーでき、動画プロンプトは月1回です。Free では MCP に接続できません。カタログ全体は30日ビルドパス($19の1回払い、自動更新なし)、Lifetime Access(先着100名の$79の1回払い)、Plus(月額 $9、いつでも解約可)です。

Cursor での使い方は https://design-layer.com/ja/articles/cursor-react-motion-components-guide で紹介しています。

無料アカウントは無料サンプル10個を無料アカウントで回数制限なくコピーでき、動画プロンプトは月1回です。Free では MCP に接続できません。カタログ全体は30日ビルドパス($19の1回払い、自動更新なし)、Lifetime Access(先着100名の$79の1回払い)、Plus(月額 $9、いつでも解約可)です。

Next.js の Framer Motion についてよくある質問

Next.js で Motion を導入するときに、よく出る質問をまとめました。

  • 同じライブラリの旧パッケージ名と新パッケージ名です。新しく始めるなら motion を入れて、motion/react から読み込みます。

    既存のコードが framer-motion なら、そのままでも動きます。読み込み元をどちらか一方にそろえることだけ守ってください。

まとめ:Next.js で Framer Motion を使う手順

  • ページ本体はサーバー側のまま、動く部品のファイルだけに "use client" を書く

  • 新しく始めるなら motion を入れ、読み込み元をそろえる

  • 入場は見出しや本文を 0.08〜0.16 秒ずつずらして出す

  • ページ途中のセクションは、画面に入ったときに一度だけ動かす

  • スクロール連動は、背景だけを数パーセント動かす

  • 「動きを減らす」設定の人には、移動の動きを止める

まずはヒーローの見出しを1つ切り出して、順番に出る入場から試してください。ヒーロー全体の組み立ては https://design-layer.com/ja/articles/nextjs-animated-hero-section-guide 、Tailwind CSS だけで作る動きは https://design-layer.com/ja/articles/nextjs-tailwind-animation-guide で解説しています。

関連ガイド