“TASTEBUDS” Food Blog Site

A learning journey from MVP development with vanilla JavaScript to redeveloping with React and Next.js.

Screenshot of the Tastebuds food blog homepage

Overview

From Class Project to Rebuild

TASTEBUDS began as a social media marketing project with my classmates, a food blog featuring restaurants and cafés in Vancouver.

At first, I had not learned React yet, so I built the site using HTML, CSS (Sass), and vanilla JavaScript. As development progressed, I wanted a more efficient workflow, which led me to learn React and Next.js. I also redesigned the UI in Figma and rebuilt the project from the ground up.

MVP

Built on Fundamentals

For the MVP, I built the project using fundamental web technologies. As I manually created blog cards with static HTML, I started to wonder, “Is there a more efficient way to replicate these?” This question led me to explore building a reusable structure using built-in browser features to dynamically generate content. This approach reduced repetition and improved scalability.

My approach

Using Vanilla JavaScript and the <template> element, I built a reusable card component.

  1. 1

    Create a data structure for posts

      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

    Use the <template> element

        <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

    Generate cards dynamically with 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)
    });

Learning and Challenges

Through the MVP, I learned the basics of DOM manipulation, reusable UI design, and separating structure, data, and styles.

At the same time, state management became complex, routing was hard to maintain, and the architecture didn’t scale well.

This led me to rebuild the project with Next.js.

Redevelopment

Scaling the Architecture

To improve the scalability of the project, I redesigned the application using React and Next.js.
The UI was rebuilt as React components, allowing the interface to be generated declaratively from data.
I also introduced Next.js to better organize the page structure and implement routing for individual posts, resulting in a more maintainable and extensible architecture.

image/svg+xml

Structured Content Management

In the MVP, blog content was stored as a front-end data array. While this worked for an early prototype, it required manual updates whenever a new post was added, which made content management less efficient as the project expanded.

This led me to rethink how post data should be handled. To create a more scalable structure, I separated content from the application code and migrated post management to Supabase.

image/svg+xml

This made the workflow easier to maintain and created a foundation that could later evolve into a CMS-like system.

  1. 1

    Design the post data structure

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

    Retrieve posts from 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 ?? [];
    }

Based on this structured data, UI components can dynamically generate blog cards and article pages.

Rendering Architecture

The UI was rebuilt using reusable React components. Post data is passed into card components to generate article cards, creating a structure that can easily scale as more content is added.

  1. PatternA: Curated layout for the homepage

    To highlight the latest articles, the homepage selects specific posts from the dataset and displays them in a curated layout.

    {posts[0] && <CardL posts={posts[0]} />}
    {posts[1] && <CardM posts={posts[1]} />}
    {posts[2] && <CardM posts={posts[2]} />}
    {posts[3] && <CardM posts={posts[3]} />}
  2. PatternB: Dynamic rendering for the blog archive

    On the blog page, filtered post data is mapped into card components to dynamically generate the article list.

    {filteredPosts.map((post) => (
      <CardM key={post.id} posts={post} />
    ))}

Dynamic Routing

Previous Problem: In the MVP, each article was managed as a separate page, making the structure difficult to scale as content grew.

Solution: I implemented Next.js Dynamic Routing using the /blog/[slug] route structure, allowing pages to be generated from a single shared template.

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

Result: By fetching content based on the slug, new articles can be added without creating new pages, improving scalability and maintainability.

Reflection

Learning Through Iteration

Through this project, I started from the fundamentals of JavaScript and continuously explored better ways to build features. I iterated through researching, experimenting, and refining my approach, and found the process of trial and error itself both engaging and rewarding as my understanding gradually deepened.

Understanding the Limits of AI-Assisted Development

I also experimented with building admin and CMS-like features using AI. However, this experience made me realize that being able to implement functionality is not the same as fully understanding the underlying architecture. In parts of the CMS, multiple features ended up being combined into a single component, which made the structure harder to maintain and scale. This highlighted the importance of component design and separation of concerns.

Moving Forward

Moving forward, I aim to deepen my understanding of architecture while using AI more intentionally, with the goal of building more scalable and maintainable systems.

Other Projects

Billow
Web dev

Billow

AI chat–based dashboard built with Next.js, focusing on conversational UI and chat interaction design.

XENO
Branding

XENO

Fitness branding project.