🙄
# React × Tailwind × Zustand でエンジニアポートフォリオを作成してみた
はじめに
エンジニアとして、自分のスキルや実績をアピールできるポートフォリオサイトを作成しました。今回の開発では、React × Tailwind CSS × Zustand を活用し、レスポンシブ対応・アニメーション付きのサイトを構築しました。本記事では、実際の開発プロセスや技術選定のポイント、工夫した点について解説します。
技術スタック
今回のポートフォリオサイトの主な技術スタックは以下の通りです。
技術 | 用途 |
---|---|
React (Vite) | フロントエンドの構築 |
Tailwind CSS | スタイリング |
Zustand | グローバル状態管理 |
Framer Motion | アニメーション実装 |
TypeScript | 型安全性の向上 |
1. プロジェクトのセットアップ
1.1 Vite + React + TypeScript のセットアップ
まず、Vite を使って React + TypeScript のプロジェクトを作成します。
npm create vite@latest my-portfolio --template react-ts
cd my-portfolio
npm install
1.2 必要なパッケージのインストール
npm install tailwindcss framer-motion zustand lucide-react
npx tailwindcss init -p
Tailwind CSS の設定ファイル tailwind.config.js
を開き、以下のように修正します。
module.exports = {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [],
};
index.css
に Tailwind のベーススタイルを適用します。
@tailwind base;
@tailwind components;
@tailwind utilities;
2. ナビゲーションバーの作成
2.1 Zustand でメニューの開閉を管理
src/store/useMenuStore.ts
に Zustand を使った状態管理を実装します。
import create from "zustand";
interface MenuState {
isOpen: boolean;
toggleMenu: () => void;
closeMenu: () => void;
}
const useMenuStore = create<MenuState>((set) => ({
isOpen: false,
toggleMenu: () => set((state) => ({ isOpen: !state.isOpen })),
closeMenu: () => set({ isOpen: false }),
}));
export default useMenuStore;
2.2 ナビゲーションバーの実装
ナビゲーションバー (src/components/Navbar.tsx
) では、ハンバーガーメニューが開閉するアニメーションを Framer Motion で追加しています。
import { motion, AnimatePresence } from "framer-motion";
import { Menu, X } from "lucide-react";
import useMenuStore from "../store/useMenuStore";
const Navbar = ({ scrollToSection, activeSection }) => {
const { isOpen, toggleMenu, closeMenu } = useMenuStore();
return (
<nav className="fixed top-0 left-0 w-full bg-white/80 backdrop-blur-sm border-b z-50">
<div className="max-w-6xl mx-auto px-4 flex items-center justify-between h-16 w-full">
<h1 className="text-lg font-bold">My Portfolio</h1>
<motion.button onClick={toggleMenu} className="md:hidden">
{isOpen ? <X size={32} /> : <Menu size={32} />}
</motion.button>
</div>
<AnimatePresence>
{isOpen && (
<motion.div
className="fixed top-0 right-0 w-3/4 h-screen bg-black bg-opacity-80 flex flex-col items-center justify-center"
initial={{ x: "100%", opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
exit={{ x: "100%", opacity: 0 }}
transition={{ duration: 0.4, ease: "easeInOut" }}
>
<ul className="text-white text-xl space-y-4">
{["home", "profile", "skill", "portfolio", "contact"].map((item) => (
<li key={item}>
<button onClick={() => { scrollToSection(item); closeMenu(); }}>
{item.toUpperCase()}
</button>
</li>
))}
</ul>
</motion.div>
)}
</AnimatePresence>
</nav>
);
};
export default Navbar;
3. ポートフォリオ一覧の実装
ポートフォリオ一覧を src/components/Portfolio.tsx
に実装しました。
const portfolioItems = [
{ title: "Project 1", description: "プロジェクト1の説明", image: "/assets/images/project1.jpg", link: "#" },
{ title: "Project 2", description: "プロジェクト2の説明", image: "/assets/images/project2.jpg", link: "#" },
];
const Portfolio = () => {
return (
<section id="portfolio" className="py-20">
<div className="max-w-6xl mx-auto px-4">
<h2 className="text-3xl font-bold text-center mb-4">PORTFOLIO</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{portfolioItems.map((item, index) => (
<a key={index} href={item.link} className="group relative block bg-white rounded-lg shadow-lg overflow-hidden">
<img src={item.image} alt={item.title} className="w-full h-48 object-cover" />
<div className="p-6">
<h3 className="text-xl font-bold mb-2">{item.title}</h3>
<p className="text-sm text-gray-600">{item.description}</p>
</div>
</a>
))}
</div>
</div>
</section>
);
};
export default Portfolio;
まとめ
今回の開発では、React × Tailwind CSS × Zustand × Framer Motion を活用し、スタイリッシュで使いやすいポートフォリオサイトを構築しました。カスタマイズのしやすさを重視し、管理画面の導入など今後の拡張性も考慮しています。
備忘録として残しておきます。
Discussion