2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Next.js × Tailwind CSS】勉強を楽しくする「箱庭」ポートフォリオを作ってみる:第1回

2
Last updated at Posted at 2026-05-08

経緯

初めまして。エンジニア1年生のシャロと申します。
IT未経験からSESとして案件に参画し、日々奮闘しています。

現在LPICの勉強中なのですが、コマンド暗記の連続で少し飽き……息抜きが必要になりました。そこで、モチベーション維持と技術力アップを兼ねて、以前から興味があったフロントエンド(React/Next.js)の勉強を始めてみることにしました。

コンセプト:Re:Garden

「勉強時間が視覚化されるとやる気が出る」という自分の性格を活かし、「資格勉強が楽しく続けられる習慣化アプリ」を制作します。

テーマ: 学ぶことで荒廃した世界に生命が戻る。

体験: 学習記録を付けると、10×10のグリッド(世界)が「荒廃 → 草 → 花」と再生していく。

ユーザー体験

1.学習記録
2.学習量表示
3.Garden表示
4.学習で世界が少しずつ蘇る

MVP要件

10×10グリッド表示
タイル100個
CSS Gridで表示

タイルの状態
状態 内容
dead 荒廃
grass
flower

dead → grass → flower  へ遷移する

Water
勉強量によって水が与えられる
リセットボタン
世界の初期化
セーブ機能
localStorageで保存
レスポンシブ
PCメイン
スマホは軽く
保守性
component分割
state分離
型定義

MVPループ

勉強

Water獲得

タイルクリック

成長

癒し

採用技術スタック

分類 技術
Framework Next.js
UI React
Language TypeScript
Styling TailwindCSS
State useState
animation CSS Transition
Storage localStorage
Deploy Vercel

難しい技術は使わず、まずは基礎的な技術かつ実務でもよくつかわれるものを選定しました

コンポーネント構成

src/
 app/
  page.tsx
  globals.css

 components/
  layout/
   Header.tsx
   GameLayout.tsx
 
  game/
   Grid.tsx
   Tile.tsx
   ManaBar.tsx
   ControlPanel.tsx
   TileEffects.tsx
   BackgroundEffects.tsx
 
  ui/
    Button.tsx
    Panel.tsx
    GlowText.tsx

 hooks/
  useMap.ts
  useLocalStorage.ts

 lib/
  map.ts
  tile.ts

 types/
  game.ts

 constants/
  colors.ts

 styles/
   animations.css

AIと相談してみたところ、ひとまずこのような構成にしてみることに

環境構築

Node.jsは入っていたので省略
ターミナルで以下を実行

npx create-next-app@latest regarden
設定
✔ TypeScript → Yes
✔ ESLint → Yes
✔ Tailwind CSS → Yes
✔ src/ directory → Yes
✔ App Router → Yes
✔ Turbopack → Yes
✔ import alias → Yes
起動
npm run dev

http://localhost:3000

その後、コンポーネント構成の通りにディレクトリをつくっていきます

ディレクトリ構成の罠

AIと相談して決めた構成で進めようとしたところ、一点トラブルが。
create-next-app 時の選択によっては、/src ディレクトリが作成されず、ルート直下に /app が配置されることがあります。今回は以下の構成で進めることにしました。

起動
regarden/
├ app/
components/
├ game/
│ ├ Grid.tsx
│ ├ Tile.tsx
│ ├ WaterBar.tsx
│ ├ StudyForm.tsx
│ └ StatusPanel.tsx
│
├ layout/
│ ├ Header.tsx
│ └ GameLayout.tsx
│
└ ui/
│ ├ Button.tsx
│ └ Panel.tsx
├ hooks/
├ lib/
├ types/
├ constants/
└ styles/

起動した画面を見てみる

page.tsxがいつものNext.jsくんの画面だったので、見た目だけ簡単に調整してみました

app/page.tsx

export default function Home() {
  return (
    <main className="flex min-h-screen items-center justify-center bg-slate-950">
      <h1 className="text-6xl font-bold text-cyan-300">
        Re:Garden
      </h1>
    </main>
  )
}

image.png

まだ題名だけですがそれっぽい……!
直感的に書けるのでやっぱりtailwindCSSは優秀です

Grid実装

1.型作成

まずは「タイル」がどんなデータを持つべきか定義します。

types/game.ts
export type TileType =
  | "dead"
  | "grass"
  | "flower"
  | "tree"

export type Tile = {
  x: number
  y: number
  type: TileType
}

2.タイル作成

10×10の配列を作っています

lib/createInitialMap.ts

import { Tile } from "@/types/game"

export function createInitialMap(): Tile[][] {
  return Array.from({ length: 10 }, (_, y) =>
    Array.from({ length: 10 }, (_, x) => ({
      x,
      y,
      type: "dead",
    }))
  )
}
components/game/Tile
import { Tile as TileType } from "@/types/game"

type Props = {
  tile: TileType
}

export default function Tile({ tile }: Props) {
  return (
    <div
      className="
        h-12
        w-12
        rounded-md
        border
        border-white/10
        bg-stone-700
        transition-all
        duration-300
      "
    />
  )
}

3.Gridコンポーネント

二次元配列のままだとReactのJSX内でループ(map)を二重に回す必要があり、CSS Gridでの管理が少し複雑になります。そこで、.flat() を使って1次元の配列に変換してから表示しています。

components/game/Grid.tsx
import { Tile as TileType } from "@/types/game"
import Tile from "./Tile"

type Props = {
  map: TileType[][]
}

export default function Grid({ map }: Props) {
  return (
    <div className="grid grid-cols-10 gap-1">
      {map.flat().map((tile) => (
        <Tile
          key={`${tile.x}-${tile.y}`}
          tile={tile}
        />
      ))}
    </div>
  )
}

二次元配列について

二次元配列 ([][])
エクセルの表やチェス盤のように縦×横にデータが存在するもの
二次元配列はデータとして扱いづらいので、map.flat()で配列にしている(grid-cols-10を指定しているので、10列配置されたら折り返す)


紆余曲折あり出来上がったものがこちらです

image.png

まだまだみすぼらしいですが達成感があります

まとめ

今回の実装を通じて、「型定義 → データの作成 → コンポーネント化」というReact開発の基本的な流れを体感することができました。
特に印象的だったのは、コンポーネントは定義しただけでは不十分。Propsを通じてデータを渡すことで初めて命が吹き込まれるという点です。
Propsを意識することで、コンポーネントの再利用性というReactの強みが少しずつ理解できてきた気がします。

現在はまだ静的な画面ですが、次回は useState を使った状態管理に挑戦し、実際に世界を動かしていきたいと思います。

ここから花を咲かせていくのが楽しみです!

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?