フードブログ「TASTEBUDS」— JavaScriptからReactへの学習記録

フードブログをVanilla JavaScriptからReact・Next.jsへ再構築しながら、フロントエンドの基礎を学びました。

MVP:
再開発:
image/svg+xml
役割
Web開発者 / Webデザイナー
期間
2か月、2025年10月〜12月

概要

授業プロジェクトから再開発へ

TASTEBUDSは、クラスメートと取り組んだソーシャルメディアマーケティングの課題として始まりました。バンクーバーのレストランやカフェを紹介するフードブログです。

制作当初はReactを学ぶ前だったため、HTML、CSS(Sass)、Vanilla JavaScriptでサイトを構築しました。開発を進める中で、より効率的な制作方法を求めてReactとNext.jsを学びました。FigmaでUIも再設計し、プロジェクトを一から作り直しました。

MVP

基礎技術からの構築

MVPでは、Webの基礎技術を使ってプロジェクトを構築しました。静的HTMLでブログカードを一つずつ作る中で、「もっと効率よく複製できないか」と考えるようになりました。そこで、ブラウザ標準機能を使ってコンテンツを動的に生成する、再利用可能な仕組みを試しました。重複を減らし、拡張しやすい構成にできました。

実装アプローチ

Vanilla JavaScriptと<template>要素を使い、再利用可能なカードコンポーネントを作成しました。

  1. 1

    投稿データの構造を作成

      export const posts = [
      {
        img: "https://picsum.photos/200/300",
        imgAlt: "City night view",
        title: "Exploring Vancouver at Night",
        excerpt: "A short walk through downtown Vancouver after sunset. Neon lights and quiet streets.",
        location: "Vancouver, BC",
        date: "2026-02-01",
        url: "https://example.com/article/vancouver-night"
      },
      {
        img: "https://picsum.photos/seed/picsum/200/300",
        imgAlt: "Mountain landscape",
        title: "Weekend Hiking Escape",
        excerpt: "A refreshing hike just outside the city. Perfect for a quick weekend reset.",
        location: "North Vancouver, BC",
        date: "2026-01-28",
        url: "https://example.com/article/hiking-escape"
      },
      {
        img: "https://picsum.photos/200/300?grayscale",
        imgAlt: "Cafe interior",
        title: "Hidden Cafés You Should Know",
        excerpt: "Three small cafés with great coffee and calm vibes for focused work.",
        location: "Burnaby, BC",
        date: "2026-01-20",
        url: "https://example.com/article/hidden-cafes"
      }
    ];
      },
  2. 2

    <template>要素を使用

        <template id="tpl-card">
            <article class="card">
                <figure>
                    <img class="tpl-img" src="" alt="">
                </figure>
                <div class="text-container">
                    <h3 class="tpl-title"></h3>
                    <p class="tpl-excerpt"></p>
                </div>
                <footer>
                    <div>
                        <p class="tpl-location"></p>
                        <p class="tpl-date"></p>
                    </div>
                    <div>
                        <a class="tpl-url" href="">Read</a>
                    </div>
                </footer>
            </article>
        </template>
  3. 3

    JavaScriptでカードを動的に生成

    import {posts} from "/data.js";
    
    posts.forEach(post => {
        const tplCard = document.getElementById("tpl-card");
        const content = tplCard.content.cloneNode(true);
    
        content.querySelector(".tpl-img").src = post.img;
        content.querySelector(".tpl-img").alt = post.imgAlt;
        content.querySelector(".tpl-title").textContent = post.title;
        content.querySelector(".tpl-excerpt").textContent = post.excerpt;
        content.querySelector(".tpl-location").textContent = post.location;
        content.querySelector(".tpl-date").textContent = post.date;
        content.querySelector(".tpl-url").href = post.url;
    
        document.body.appendChild(content)
    });

学びと課題

MVPを通して、DOM操作、再利用可能なUI設計、構造・データ・スタイルを分離する基礎を学びました。

一方で、状態管理が複雑になり、ルーティングの保守も難しく、拡張しにくい構成になりました。

この課題をきっかけに、Next.jsでプロジェクトを再構築しました。

Folder structure of the Tastebuds MVP built with vanilla JavaScript

再開発

拡張可能なアーキテクチャへ

プロジェクトの拡張性を高めるため、ReactとNext.jsを使ってアプリケーションを再設計しました。UIをReactコンポーネントとして再構築し、データから宣言的に画面を生成できるようにしました。さらにNext.jsでページ構成を整理し、投稿ごとのルーティングを実装することで、保守・拡張しやすいアーキテクチャに改善しました。

image/svg+xml

構造化されたコンテンツ管理

MVPでは、ブログコンテンツをフロントエンドのデータ配列として保存していました。初期の試作では機能しましたが、新しい投稿を追加するたびに手作業で更新する必要があり、規模が大きくなるにつれて管理効率が下がりました。

そこで投稿データの扱いを見直しました。より拡張しやすい構成にするため、コンテンツをアプリケーションコードから分離し、投稿管理をSupabaseへ移行しました。

image/svg+xml

これにより保守しやすいワークフローになり、将来的にCMSのような仕組みへ発展できる基盤を作れました。

  1. 1

    投稿データの構造を設計

    export type PostCard = {
      id: string;
      slug: string;
      title: string;
      excerpt: string | null;
      published_at: string | null;
      cover_image_path: string | null;
    };
  2. 2

    Supabaseから投稿を取得

    export async function getAllPosts(): Promise<PostCard[]> {
      const { data, error } = await supabase
        .from("posts")
        .select("id, slug, title, excerpt, published_at, cover_image_path")
        .order("published_at", { ascending: false });
    
      if (error) {
        console.error("Failed to fetch posts", error);
        return [];
      }
    
      return data ?? [];
    }

構造化したデータをもとに、UIコンポーネントからブログカードと記事ページを動的に生成できます。

レンダリング設計

UIを再利用可能なReactコンポーネントで再構築しました。投稿データをカードコンポーネントへ渡して記事カードを生成し、コンテンツが増えても拡張しやすい構成にしました。

Reusable React card component displaying blog post data
  1. パターンA:トップページ用に選定したレイアウト

    最新記事を目立たせるため、トップページではデータから特定の投稿を選び、編集されたレイアウトで表示します。

    {posts[0] && <CardL posts={posts[0]} />}
    {posts[1] && <CardM posts={posts[1]} />}
    {posts[2] && <CardM posts={posts[2]} />}
    {posts[3] && <CardM posts={posts[3]} />}
    Homepage displaying a curated selection of blog post cards
  2. パターンB:ブログ一覧の動的レンダリング

    ブログページでは、絞り込んだ投稿データをカードコンポーネントへ割り当て、記事一覧を動的に生成します。

    {filteredPosts.map((post) => (
      <CardM key={post.id} posts={post} />
    ))}
    Blog archive page listing article cards dynamically generated from filtered post data

動的ルーティング

以前の課題:MVPでは記事ごとに個別ページを管理していたため、コンテンツが増えるほど拡張が難しい構成でした。

解決方法:Next.jsの動的ルーティングを/blog/[slug]の構成で実装し、1つの共通テンプレートからページを生成できるようにしました。

// Static pages (MVP)          // Dynamic routing
pages/                          app/
  └── posts/                     └── blog/
      ├── post-1/                    └── [slug]/
      │     └── index.html             └── page.tsx
      ├── post-2/                         
      │     └── index.html        
      ├── post-3/                                   
      │     └── index.html
      .
      .
      . 

結果:slugに応じてコンテンツを取得することで、新しいページを個別に作らず記事を追加でき、拡張性と保守性が向上しました。

振り返り

試行錯誤を通した学び

このプロジェクトでは、JavaScriptの基礎から始め、より良い実装方法を継続的に探りました。調査、実験、改善を繰り返す中で理解が深まり、試行錯誤する過程そのものに面白さと手応えを感じました。

AI支援開発の限界を理解する

AIを使った管理画面やCMS機能の構築も試しました。しかし、機能を実装できることと、その土台となる設計を理解することは同じではないと気づきました。CMSの一部では複数の機能が1つのコンポーネントに集まり、保守・拡張しにくい構成になりました。この経験から、コンポーネント設計と関心の分離の重要性を学びました。

AI-assisted CMS admin screen for managing Tastebuds blog posts

今後に向けて

今後はAIを目的に応じて活用しながらアーキテクチャへの理解を深め、より拡張しやすく保守性の高いシステムを作ることを目指します。

その他のプロジェクト

給与確認アプリのリデザイン
UI/UX ケーススタディ

給与確認アプリのリデザイン

実際のユーザーインタビューとフィードバックをもとに、給与確認体験を調査・再設計しました。

Billow
Web開発

Billow

会話型UIとチャット体験の設計に重点を置いた、Next.js製のサブスク管理ダッシュボードです。