Motion(旧 Framer Motion)× Vite/Next/Turbopack:ツリーシェイク最適化とバンドル最小化の初期設定

Motion(旧 Framer Motion)を使用したプロジェクトでは、豊富なアニメーション機能の反面、バンドルサイズが大きくなってしまう課題があります。特に本格的な Web アプリケーション開発では、パフォーマンスとユーザー体験の両立が重要になってきますね。
本記事では、Vite、Next.js、Turbopack の各バンドラーで Motion ライブラリのツリーシェイクを効率的に行い、バンドルサイズを最小化する具体的な設定方法をご紹介します。実際のコード例とともに、各バンドラーの特性を活かした最適化テクニックを学んでいただけるでしょう。
背景
Motion(旧 Framer Motion)の概要
Motion は React アプリケーション向けの強力なアニメーションライブラリです。2024 年に Framer Motion から名称変更され、より軽量で高性能なライブラリとして生まれ変わりました。
以下の図は、Motion ライブラリの主要な機能を示しています。
mermaidflowchart TD
motion["Motion ライブラリ"] --> animations["アニメーション機能"]
motion --> gestures["ジェスチャー機能"]
motion --> layout["レイアウト機能"]
animations --> keyframes["キーフレーム"]
animations --> spring["スプリング"]
animations --> timeline["タイムライン"]
gestures --> drag["ドラッグ"]
gestures --> hover["ホバー"]
gestures --> tap["タップ"]
layout --> shared["共有レイアウト"]
layout --> reorder["リオーダー"]
layout --> presence["プレゼンス"]
Motion の豊富な機能により、従来は複雑な CSS や JavaScript で実装していたアニメーションを、宣言的で直感的な API で実現できるようになります。
バンドルサイズの重要性
Web アプリケーションのバンドルサイズは、ユーザー体験に直接影響する重要な要素です。特にモバイルユーザーや回線速度の遅い環境では、初期読み込み時間がサービスの利用継続率に大きく関わってきます。
サイズ範囲 | 読み込み時間(3G) | ユーザー体験への影響 |
---|---|---|
~ 100KB | 1 秒以内 | ★★★ 優秀 |
100-300KB | 1-3 秒 | ★★☆ 良好 |
300KB ~ | 3 秒以上 | ★☆☆ 改善必要 |
Motion(旧 Framer Motion)は多機能である反面、すべての機能を含んだ状態では 200KB 以上のサイズになることもあり、適切な最適化が必要になります。
ツリーシェイクとは何か
ツリーシェイクは、使用されていないコードを自動的に除去するバンドル最適化技術です。ES6 のインポート・エクスポート構文の静的解析により、実際に使用されている関数やクラスのみをバンドルに含めることができます。
以下の図で、ツリーシェイクの仕組みを理解しましょう。
mermaidflowchart LR
library["ライブラリ全体<br/>(200KB)"] --> treeshake["ツリーシェイク<br/>解析"]
treeshake --> used["使用中の機能<br/>(50KB)"]
treeshake --> unused["未使用の機能<br/>(150KB)"]
unused -.->|除去| bundle["最終バンドル<br/>(50KB)"]
used --> bundle
この技術により、Motion ライブラリの必要な部分のみを含んだ軽量なバンドルを生成できるのです。
課題
Motion の大きなバンドルサイズ
Motion ライブラリは豊富な機能を提供する反面、デフォルトインポートを使用すると不要な機能まで含まれてしまいます。
一般的なインポート方法での問題点を見てみましょう。
typescript// 問題のあるインポート方法
import { motion } from 'motion';
このインポート方法では、motion オブジェクトに含まれるすべての機能がバンドルに含まれてしまいます。実際に使用するのがアニメーション機能のみであっても、ジェスチャーやレイアウト機能も一緒にバンドルされてしまうのです。
不要なコードの混入問題
Motion ライブラリの機能別サイズを以下の表で確認してください。
機能分類 | サイズ | 使用頻度 | 最適化の効果 |
---|---|---|---|
コアアニメーション | 45KB | ★★★ 高 | 必須 |
ジェスチャー機能 | 35KB | ★★☆ 中 | 条件付き |
レイアウト機能 | 55KB | ★☆☆ 低 | 大きな削減 |
3D 変換機能 | 25KB | ★☆☆ 低 | 大きな削減 |
SVG アニメーション | 20KB | ★☆☆ 低 | 大きな削減 |
このように、プロジェクトで実際に使用する機能は限られていることが多く、適切な設定により大幅なサイズ削減が可能になります。
パフォーマンスへの影響
バンドルサイズの増加は、以下のようなパフォーマンス問題を引き起こします。
mermaidflowchart TD
large["大きなバンドル"] --> download["ダウンロード時間増加"]
large --> parse["JavaScriptパース時間増加"]
large --> memory["メモリ使用量増加"]
download --> fcp["First Contentful Paint遅延"]
parse --> tti["Time to Interactive遅延"]
memory --> runtime["ランタイムパフォーマンス低下"]
fcp --> ux["ユーザー体験の悪化"]
tti --> ux
runtime --> ux
特にモバイルデバイスでは、CPU とメモリの制約により、これらの影響がより顕著に現れます。
解決策
ツリーシェイク設定の最適化
効果的なツリーシェイクを実現するには、バンドラーの設定とインポート方法の両方を最適化する必要があります。
基本的な最適化の流れを図で確認しましょう。
mermaidflowchart TD
config["バンドラー設定"] --> import["インポート方法"]
import --> analysis["静的解析"]
analysis --> optimization["コード最適化"]
optimization --> bundle["軽量バンドル"]
config --> sideeffects["sideEffects設定"]
config --> modules["ES Modules対応"]
import --> named["名前付きインポート"]
import --> specific["特定機能インポート"]
各段階での設定により、効率的なツリーシェイクが実現されます。
インポート方法の改善
Motion ライブラリを効率的に使用するための推奨インポート方法をご紹介します。
推奨されるインポート方法
typescript// 特定の機能のみをインポート
import { animate } from 'motion';
import { scroll } from 'motion';
この方法により、必要な機能のみがバンドルに含まれます。
さらに詳細な機能別インポート
typescript// アニメーション機能のみ
import { animate, timeline } from 'motion/animation';
// ジェスチャー機能のみ
import { drag } from 'motion/gestures';
// DOM要素向け機能のみ
import { motion } from 'motion/dom';
機能別のサブパッケージを使用することで、より細かい制御が可能になります。
バンドラー固有の設定
各バンドラーには、ツリーシェイクを効率化するための固有の設定があります。それぞれの特徴を活かした最適化アプローチを採用することが重要です。
具体例
Vite での設定例
Vite は高速な開発サーバーとビルドツールとして人気が高く、ES Modules をネイティブサポートしているため、効率的なツリーシェイクが可能です。
vite.config.js の基本設定
javascriptimport { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
build: {
// ツリーシェイクの最適化
rollupOptions: {
output: {
manualChunks: {
// Motionを別チャンクに分離
motion: ['motion'],
},
},
},
},
});
この設定により、Motion ライブラリを独立したチャンクとして分離し、キャッシュ効率を向上させます。
package.json での sideEffects 設定
json{
"name": "motion-vite-app",
"sideEffects": false,
"dependencies": {
"motion": "^10.18.0",
"react": "^18.2.0"
}
}
sideEffects を false に設定することで、より積極的なツリーシェイクが有効になります。
実際のコンポーネントでの使用例
typescript// 効率的なインポート方法
import { animate } from 'motion';
import { useEffect, useRef } from 'react';
const AnimatedComponent = () => {
const elementRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (elementRef.current) {
// 必要最小限の機能のみを使用
animate(
elementRef.current,
{ opacity: [0, 1], y: [20, 0] },
{ duration: 0.5 }
);
}
}, []);
return <div ref={elementRef}>アニメーション要素</div>;
};
Next.js での設定例
Next.js はサーバーサイドレンダリングとクライアントサイドハイドレーションの両方に対応する必要があるため、特別な配慮が必要です。
next.config.js の最適化設定
javascript/** @type {import('next').NextConfig} */
const nextConfig = {
// 実験的なTurbopack使用時の設定
experimental: {
turbopack: true,
},
// Webpack設定のカスタマイズ
webpack: (config, { isServer }) => {
// クライアントサイドのみでMotionを使用
if (!isServer) {
config.optimization.splitChunks.chunks = 'all';
config.optimization.splitChunks.cacheGroups = {
motion: {
test: /[\\/]node_modules[\\/]motion[\\/]/,
name: 'motion',
chunks: 'all',
},
};
}
return config;
},
};
module.exports = nextConfig;
この設定により、Motion ライブラリをクライアントサイドでのみ読み込み、サーバーサイドでの不要な処理を回避できます。
動的インポートを活用したコンポーネント
typescriptimport dynamic from 'next/dynamic';
import { Suspense } from 'react';
// Motion機能を動的にインポート
const MotionComponent = dynamic(
() => import('../components/AnimatedComponent'),
{
ssr: false, // サーバーサイドレンダリングを無効化
loading: () => <div>読み込み中...</div>,
}
);
const Page = () => {
return (
<main>
<h1>メインコンテンツ</h1>
<Suspense
fallback={<div>アニメーション読み込み中...</div>}
>
<MotionComponent />
</Suspense>
</main>
);
};
動的インポートにより、アニメーション機能が必要な時のみ Motion ライブラリを読み込むことができます。
App Router での使用例
typescript'use client'; // クライアントコンポーネントとして明示
import { motion } from 'motion/dom';
import { useState } from 'react';
const InteractiveButton = () => {
const [isPressed, setIsPressed] = useState(false);
return (
<motion.button
animate={{
scale: isPressed ? 0.95 : 1,
backgroundColor: isPressed ? '#3b82f6' : '#6366f1',
}}
transition={{ duration: 0.1 }}
onMouseDown={() => setIsPressed(true)}
onMouseUp={() => setIsPressed(false)}
onMouseLeave={() => setIsPressed(false)}
>
インタラクティブボタン
</motion.button>
);
};
Turbopack での設定例
Turbopack は次世代のバンドラーとして、Rust ベースの高速処理とインクリメンタルビルドが特徴です。
turbopack.config.js の基本設定
javascriptmodule.exports = {
// ES Modulesの最適化
resolve: {
mainFields: ['module', 'main'],
conditionNames: ['import', 'require'],
},
// バンドル最適化
optimization: {
treeShaking: true,
sideEffects: false,
},
};
Turbopack では ES Modules が優先され、効率的なツリーシェイクが自動的に実行されます。
パッケージの設定調整
json{
"name": "motion-turbopack-app",
"type": "module",
"dependencies": {
"motion": "^10.18.0"
},
"devDependencies": {
"@turbo/types": "^1.10.0"
}
}
type フィールドを module に設定することで、ES Modules を前提とした最適化が有効になります。
Turbopack 専用のコンポーネント最適化
typescript// Turbopackの最適化を活用したインポート
import { animate, scroll, timeline } from 'motion';
const TurboOptimizedComponent = () => {
// 複数のMotion機能を効率的に使用
const handleComplexAnimation = async () => {
// タイムラインアニメーション
await timeline([
['#element1', { opacity: 1 }, { duration: 0.3 }],
['#element2', { y: 0 }, { duration: 0.4, at: 0.2 }],
]);
// スクロール連動アニメーション
scroll(animate('#element3', { rotateY: [0, 360] }), {
target: document.querySelector('#scroll-container'),
});
};
return (
<div>
<div id='element1'>要素1</div>
<div id='element2'>要素2</div>
<div id='element3'>要素3</div>
<button onClick={handleComplexAnimation}>
アニメーション実行
</button>
</div>
);
};
Turbopack では、複数の機能を同時にインポートしても効率的な最適化が行われます。
パフォーマンス測定とバンドル分析
typescript// 開発時のバンドル分析
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
const analyzeBundle = () => {
if (process.env.ANALYZE === 'true') {
return new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false,
reportFilename: 'bundle-report.html',
});
}
return null;
};
定期的なバンドル分析により、最適化の効果を定量的に測定できます。
まとめ
設定のポイント整理
Motion(旧 Framer Motion)の効果的な最適化には、以下の重要なポイントがあります。
最適化項目 | 効果 | 実装難易度 | 推奨度 |
---|---|---|---|
名前付きインポート | ★★★ | ★☆☆ | 必須 |
機能別インポート | ★★★ | ★★☆ | 強く推奨 |
動的インポート | ★★☆ | ★★★ | 推奨 |
バンドラー設定 | ★★★ | ★★☆ | 強く推奨 |
sideEffects 設定 | ★★☆ | ★☆☆ | 推奨 |
特に名前付きインポートと機能別インポートは、実装が簡単でありながら大きな効果が期待できるため、最優先で取り組むべき項目です。
パフォーマンス向上効果
適切な最適化を実施することで、以下のような顕著な改善効果が得られます。
バンドルサイズの削減効果
- 最適化前:約 200KB
- 最適化後:約 50-80KB
- 削減率:60-75%
読み込み時間の改善
- 3G 回線での初期読み込み:3 秒 → 1 秒
- 4G 回線での初期読み込み:1 秒 → 0.3 秒
- WiFi 環境での初期読み込み:0.5 秒 → 0.1 秒
これらの改善により、ユーザー体験の大幅な向上が実現できるでしょう。継続的な最適化と定期的な効果測定により、常に最良のパフォーマンスを維持することが重要です。
バンドラーの進化とともに、より効率的な最適化手法も登場してくるため、最新の技術動向にも注目していただければと思います。
関連リンク
- article
Motion(旧 Framer Motion)× Vite/Next/Turbopack:ツリーシェイク最適化とバンドル最小化の初期設定
- article
Motion(旧 Framer Motion)でカクつき・ちらつきを潰す:レイアウトシフトと FLIP の落とし穴
- article
Motion(旧 Framer Motion)アーキテクチャ概説:Renderer と Animation Engine を俯瞰する
- article
Motion(旧 Framer Motion)Gesture アニメーション実践:whileHover/whileTap/whileFocus の設計術
- article
Motion(旧 Framer Motion)基本 API 徹底解説:motion 要素・initial/animate/exit の正しい使い方
- article
移行ガイド:Framer Motion から Motion への変更点と対応策
- article
【2025 年 10 月版】 Claude Sonnet 4.5 登場! Claude Pro でも使える!Claude Code のアップデート手順まで紹介
- article
Turborepo で Zustand スライスをパッケージ化:Monorepo 運用の初期設定
- article
Nuxt を macOS + yarn で最短構築:ESLint/Prettier/TS 設定まで一気通貫
- article
キャッシュ比較:WordPress で WP Rocket/LiteSpeed/W3TC を検証
- article
Nginx を macOS で本番級に構築:launchd/ログローテーション/権限・署名のベストプラクティス
- article
WebSocket を NGINX/HAProxy で終端する設定例:アップグレードヘッダーとタイムアウト完全ガイド
- blog
iPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
- blog
Googleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
- blog
【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
- blog
Googleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
- blog
Pixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
- blog
フロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来