Vibe coding with AI course · Module 13: Twitch Clone

CI/CD Pipeline - GitHub Actions

1 min read

Automated deployment.

GitHub Actions Workflow

name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '20'
      - run: npm ci
      - run: npm test
      - run: npm run build
      - uses: amondnet/vercel-action@v25
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}

See you soon!

Code for this lesson: App.tsx
'use client';

import { useState } from 'react';

interface Category {
  id: string;
  name: string;
  boxArtUrl: string;
  viewerCount: number;
  tags: string[];
}

const mockCategories: Category[] = [
  {
    id: '1',
    name: 'Software & Game Development',
    boxArtUrl: 'https://images.unsplash.com/photo-1517694712202-14dd9538aa97?w=300&h=400&fit=crop',
    viewerCount: 12547,
    tags: ['Programming', 'Tutorial']
  },
  {
    id: '2',
    name: 'Science & Technology',
    boxArtUrl: 'https://images.unsplash.com/photo-1518770660439-4636190af475?w=300&h=400&fit=crop',
    viewerCount: 8932,
    tags: ['Educational', 'Tech']
  },
  {
    id: '3',
    name: 'Just Chatting',
    boxArtUrl: 'https://images.unsplash.com/photo-1519389950473-47ba0277781c?w=300&h=400&fit=crop',
    viewerCount: 45123,
    tags: ['IRL', 'Talk Show']
  },
  {
    id: '4',
    name: 'Music & Performing Arts',
    boxArtUrl: 'https://images.unsplash.com/photo-1511379938547-c1f69419868d?w=300&h=400&fit=crop',
    viewerCount: 6789,
    tags: ['Music', 'Performance']
  },
  {
    id: '5',
    name: 'Art',
    boxArtUrl: 'https://images.unsplash.com/photo-1460661419201-fd4cecdf8a8b?w=300&h=400&fit=crop',
    viewerCount: 4321,
    tags: ['Creative', 'Drawing']
  },
  {
    id: '6',
    name: 'Makers & Crafting',
    boxArtUrl: 'https://images.unsplash.com/photo-1452860606245-08befc0ff44b?w=300&h=400&fit=crop',
    viewerCount: 2156,
    tags: ['DIY', 'Crafts']
  }
];

export default function BrowsePage() {
  const [activeTab, setActiveTab] = useState<'categories' | 'live'>('categories');

  return (
    <div className="min-h-screen bg-gray-950 text-white p-8">
      <h1 className="text-3xl font-bold mb-6">Browse</h1>

      {/* Tabs */}
      <div className="flex gap-6 mb-8 border-b border-gray-800">
        {[
          { key: 'categories', label: 'Categories' },
          { key: 'live', label: 'Live Channels' }
        ].map(tab => (
          <button
            key={tab.key}
            onClick={() => setActiveTab(tab.key as any)}
            className={`pb-3 font-semibold ${
              activeTab === tab.key
                ? 'border-b-2 border-purple-500'
                : 'text-gray-400 hover:text-white'
            }`}
          >
            {tab.label}
          </button>
        ))}
      </div>

      {/* Categories Grid */}
      {activeTab === 'categories' && (
        <div className="grid grid-cols-6 gap-4">
          {mockCategories.map(category => (
            <div key={category.id} className="cursor-pointer group">
              <div className="relative mb-2 overflow-hidden rounded">
                <img
                  src={category.boxArtUrl}
                  alt={category.name}
                  className="w-full aspect-[3/4] object-cover group-hover:scale-110 transition"
                />
                <div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black to-transparent p-2">
                  <div className="text-xs text-gray-300">
                    {category.viewerCount.toLocaleString()} viewers
                  </div>
                </div>
              </div>
              <h3 className="font-semibold text-sm mb-1">{category.name}</h3>
              <div className="flex flex-wrap gap-1">
                {category.tags.map(tag => (
                  <span key={tag} className="text-xs bg-gray-800 px-2 py-0.5 rounded">
                    {tag}
                  </span>
                ))}
              </div>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

Spotted a mistake in this lesson?

Check yourself

Answer the questions from this lesson. Pick an answer to see right away whether it is correct.

  1. 1. What does the CI/CD pipeline with GitHub Actions described in the lesson do?

  2. 2. Which event triggers the Deploy workflow (the on: section)?

Hands-on tasks in the game

  • Vertical ordering

    Order the steps of the deploy job in the workflow:

  • Horizontal ordering

    Arrange the reference to the Vercel token from secrets (one line): ${{ secrets.VERCEL_TOKEN }}

Useful articles