바이브코딩 연수
공학하는 기술쌤
고태열
경기도교육청 소속 기술교사
공인된 전문성을 바탕으로
기술교육의 미래를 그립니다.
공학하는 기술쌤 블로그 QR 코드 [공학하는 기술쌤 BLOG]
들어가며

오늘은 코드를 한 줄도 쓰지 않고 게임을 만듭니다

생성형 AI에게 원하는 것을 말로 설명하고, 결과를 확인하고, 다시 말로 다듬어 웹에서 돌아가는 게임을 완성합니다. 이 과정을 겪은 뒤, 그것이 왜 "바이브코딩"이라 불리는지 정리합니다.

구간하는 일
실습 1. 테트리스 만들기제미나이에게 코드를 받아 파일로 저장해 실행하고, 말로 기능을 덧붙입니다.
바이브코딩 이해하기방금 한 과정의 이름과 원리, 달라지는 사람의 역할과 한계를 정리합니다.
실습 2. 제미나이 캔버스파일 저장 없이 대화창 옆에서 바로 실행하는 방법을 익힙니다.
실습 3. 나만의 게임요청문을 직접 설계해 나만의 게임을 만들고 다듬습니다.

준비물

구글 계정제미나이 로그인에 필요합니다.
크롬 브라우저제미나이와 만든 게임을 실행합니다.
메모장받은 코드를 HTML 파일로 저장할 때 씁니다. (맥은 텍스트 편집기)
진행 방법위쪽 다음 버튼으로 한 단계씩 넘어가세요. 단계마다 확인 목록이 있고, 체크한 내용은 이 브라우저에 저장되어 창을 닫았다 열어도 이어서 할 수 있습니다. 왼쪽 목차에서 원하는 단계로 바로 이동할 수도 있습니다.

확인

실습 1. 테트리스 만들기

제미나이에 들어가 로그인합니다

  1. 아래 버튼을 눌러 제미나이를 새 탭으로 엽니다.
  2. 오른쪽 위 로그인을 눌러 구글 계정으로 로그인합니다.
로그인하는 이유로그인하면 대화가 이어집니다. 뒤에서 "방금 만든 게임에 기능을 추가해줘"라고 요청할 때, 제미나이가 앞서 만든 코드를 기억하고 이어서 고쳐 줍니다.

확인

실습 1. 테트리스 만들기

테트리스 코드를 요청합니다

아래 문장을 복사해 제미나이 입력창에 붙여 넣고 보냅니다.

첫 번째 요청
내가 지금 웹페이지에서 작동할 수 있는 테트리스 게임을 만들어보려고해

HTML 형식으로 테트리스 게임 코드를 짜줘

잠시 뒤 긴 코드와 함께 설명이 나옵니다. 코드를 이해하려고 애쓰지 않아도 됩니다. 다음 단계에서 이 코드를 파일로 저장합니다.

요청문에 담긴 것무엇을(테트리스 게임), 어디서(웹페이지에서 작동), 어떤 형식으로(HTML 코드1)를 모두 말했습니다. 이 세 가지가 분명할수록 한 번에 쓸 만한 결과가 나옵니다.
1 HTML이란?

HTML(HyperText Markup Language)은 웹페이지를 만드는 기본 언어입니다. 우리가 인터넷에서 보는 거의 모든 페이지가 HTML로 되어 있고, 크롬 같은 브라우저는 HTML 파일을 읽어 화면에 보여 줍니다.

HTML뼈대제목, 버튼, 게임 화면처럼 무엇이 어디에 있는지 정합니다.
CSS옷색깔, 크기, 배치처럼 어떻게 보일지 꾸밉니다.
자바스크립트움직임블록이 떨어지고 키보드에 반응하는 동작을 만듭니다.

"HTML 형식으로 짜줘"라고 하면 이 세 가지를 파일 하나에 모두 담아 줍니다. 그래서 파일 하나만 있으면 별도 프로그램 없이 브라우저에서 바로 실행할 수 있습니다.

확인

실습 1. 테트리스 만들기

받은 코드를 HTML 파일로 저장합니다

  1. 제미나이 답변의 코드 상자 오른쪽 위 복사 아이콘을 눌러 코드 전체를 복사합니다.
  2. 메모장을 열고 붙여 넣습니다.
  3. 파일 → 다른 이름으로 저장을 누르고, 아래 세 가지를 맞춰 저장합니다.
저장할 때 꼭 확인할 세 가지
파일 이름tetris.html확장자 .html까지 직접 입력합니다.
파일 형식모든 파일 (*.*)텍스트 문서로 두면 tetris.html.txt가 되어 실행되지 않습니다.
인코딩UTF-8다른 인코딩이면 한글이 깨집니다.

왜 이렇게 저장해야 할까요?

메모장을 쓰는 이유

코드는 글자만 있는 순수한 텍스트여야 합니다. 한글이나 워드는 글꼴, 줄 간격 같은 서식 정보를 파일 안에 함께 저장해서 코드가 망가집니다. 메모장은 입력한 글자만 그대로 저장하므로 코드를 담기에 알맞습니다.

파일 이름: .html

컴퓨터는 파일 이름 끝의 확장자를 보고 어떤 프로그램으로 열지 정합니다. .txt는 메모장으로, .html은 브라우저로 엽니다. 그래서 확장자를 .html로 붙여야 더블클릭했을 때 게임이 실행됩니다.

파일 형식: 모든 파일

메모장은 기본적으로 저장하는 파일 끝에 .txt를 자동으로 붙입니다. 이름을 tetris.html로 써도 실제로는 tetris.html.txt가 됩니다. "모든 파일"을 고르면 우리가 쓴 이름 그대로 저장됩니다.

인코딩: UTF-8

인코딩은 글자를 컴퓨터가 저장하는 숫자로 바꾸는 약속입니다. UTF-8은 한글을 포함한 전 세계 문자를 표현하는 웹의 표준 약속입니다. 코드 안에도 "UTF-8로 읽으라"는 문구(charset="UTF-8")가 들어 있어서, 같은 약속으로 저장해야 한글이 깨지지 않습니다.

맥에서는 이렇게 저장하세요
텍스트 편집기를 열고 먼저 포맷 → 일반 텍스트로 만들기를 누른 뒤 붙여 넣습니다. 이름을 tetris.html로 저장하고, ".txt를 붙일까요?"라고 물으면 .html 사용을 누릅니다.
파일 아이콘이 메모장 모양이에요
파일 형식을 "텍스트 문서"로 둔 채 저장해서 실제 이름이 tetris.html.txt가 된 경우입니다. 다시 저장하면서 파일 형식을 모든 파일 (*.*)로 바꿔 주세요.

확인

실습 1. 테트리스 만들기

파일을 열어 게임을 실행합니다

저장한 tetris.html을 더블클릭하면 브라우저에서 게임이 열립니다.

← →좌우로 이동 ↓빠르게 내리기 ↑ 또는 W회전

AI가 만든 코드마다 조작키가 조금씩 다를 수 있습니다. 게임 화면이나 제미나이 답변의 설명을 확인하세요.

예제 코드 1: 심플 테트리스

직접 진행하기 어려울 때

연수 환경에 따라 로그인이 어렵거나, 코드를 받는 데 시간이 오래 걸릴 수 있습니다. 그럴 때는 직접 받은 코드 대신 아래 예제 코드를 사용하세요. ▶ 바로 플레이를 누르면 저장하지 않고 이 화면에서 바로 게임을 해 볼 수 있습니다.

tetris.html
게임 화면을 한 번 클릭한 뒤 키보드로 조작하세요.
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>심플 테트리스</title>
    <style>
        body {
            background: #202028;
            color: #fff;
            font-family: sans-serif;
            font-size: 2em;
            text-align: center;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            height: 100vh;
            margin: 0;
        }
        #game-container {
            position: relative;
            border: 2px solid #fff;
        }
        canvas {
            display: block;
            background-color: #000;
        }
        .score {
            margin-bottom: 10px;
        }
    </style>
</head>
<body>
    <div class="score" id="score">0</div>
    <div id="game-container">
        <canvas id="tetris" width="240" height="400"></canvas>
    </div>

    <script>
        const canvas = document.getElementById('tetris');
        const context = canvas.getContext('2d');
        const scoreElement = document.getElementById('score');

        context.scale(20, 20);

        // 라인 삭제 로직
        function arenaSweep() {
            let rowCount = 1;
            outer: for (let y = arena.length - 1; y > 0; --y) {
                for (let x = 0; x < arena[y].length; ++x) {
                    if (arena[y][x] === 0) {
                        continue outer;
                    }
                }
                const row = arena.splice(y, 1)[0].fill(0);
                arena.unshift(row);
                ++y;
                player.score += rowCount * 10;
                rowCount *= 2;
            }
            updateScore();
        }

        // 충돌 감지
        function collide(arena, player) {
            const [m, o] = [player.matrix, player.pos];
            for (let y = 0; y < m.length; ++y) {
                for (let x = 0; x < m[y].length; ++x) {
                    if (m[y][x] !== 0 &&
                       (arena[y + o.y] && arena[y + o.y][x + o.x]) !== 0) {
                        return true;
                    }
                }
            }
            return false;
        }

        // 보드 생성
        function createMatrix(w, h) {
            const matrix = [];
            while (h--) {
                matrix.push(new Array(w).fill(0));
            }
            return matrix;
        }

        // 블록 모양 정의
        function createPiece(type) {
            if (type === 'I') {
                return [
                    [0, 1, 0, 0],
                    [0, 1, 0, 0],
                    [0, 1, 0, 0],
                    [0, 1, 0, 0],
                ];
            } else if (type === 'L') {
                return [
                    [0, 2, 0],
                    [0, 2, 0],
                    [0, 2, 2],
                ];
            } else if (type === 'J') {
                return [
                    [0, 3, 0],
                    [0, 3, 0],
                    [3, 3, 0],
                ];
            } else if (type === 'O') {
                return [
                    [4, 4],
                    [4, 4],
                ];
            } else if (type === 'Z') {
                return [
                    [5, 5, 0],
                    [0, 5, 5],
                    [0, 0, 0],
                ];
            } else if (type === 'S') {
                return [
                    [0, 6, 6],
                    [6, 6, 0],
                    [0, 0, 0],
                ];
            } else if (type === 'T') {
                return [
                    [0, 7, 0],
                    [7, 7, 7],
                    [0, 0, 0],
                ];
            }
        }

        function draw() {
            context.fillStyle = '#000';
            context.fillRect(0, 0, canvas.width, canvas.height);
            drawMatrix(arena, {x: 0, y: 0});
            drawMatrix(player.matrix, player.pos);
        }

        function drawMatrix(matrix, offset) {
            matrix.forEach((row, y) => {
                row.forEach((value, x) => {
                    if (value !== 0) {
                        context.fillStyle = colors[value];
                        context.fillRect(x + offset.x, y + offset.y, 1, 1);
                    }
                });
            });
        }

        // 데이터 합치기
        function merge(arena, player) {
            player.matrix.forEach((row, y) => {
                row.forEach((value, x) => {
                    if (value !== 0) {
                        arena[y + player.pos.y][x + player.pos.x] = value;
                    }
                });
            });
        }

        // 회전 로직
        function rotate(matrix, dir) {
            for (let y = 0; y < matrix.length; ++y) {
                for (let x = 0; x < y; ++x) {
                    [matrix[x][y], matrix[y][x]] = [matrix[y][x], matrix[x][y]];
                }
            }
            if (dir > 0) {
                matrix.forEach(row => row.reverse());
            } else {
                matrix.reverse();
            }
        }

        function playerDrop() {
            player.pos.y++;
            if (collide(arena, player)) {
                player.pos.y--;
                merge(arena, player);
                playerReset();
                arenaSweep();
                updateScore();
            }
            dropCounter = 0;
        }

        function playerMove(offset) {
            player.pos.x += offset;
            if (collide(arena, player)) {
                player.pos.x -= offset;
            }
        }

        function playerReset() {
            const pieces = 'TJLOSZI';
            player.matrix = createPiece(pieces[pieces.length * Math.random() | 0]);
            player.pos.y = 0;
            player.pos.x = (arena[0].length / 2 | 0) - (player.matrix[0].length / 2 | 0);
            if (collide(arena, player)) {
                arena.forEach(row => row.fill(0));
                player.score = 0;
                updateScore();
            }
        }

        function playerRotate(dir) {
            const pos = player.pos.x;
            let offset = 1;
            rotate(player.matrix, dir);
            while (collide(arena, player)) {
                player.pos.x += offset;
                offset = -(offset + (offset > 0 ? 1 : -1));
                if (offset > player.matrix[0].length) {
                    rotate(player.matrix, -dir);
                    player.pos.x = pos;
                    return;
                }
            }
        }

        let dropCounter = 0;
        let dropInterval = 1000;
        let lastTime = 0;

        function update(time = 0) {
            const deltaTime = time - lastTime;
            lastTime = time;

            dropCounter += deltaTime;
            if (dropCounter > dropInterval) {
                playerDrop();
            }

            draw();
            requestAnimationFrame(update);
        }

        function updateScore() {
            scoreElement.innerText = player.score;
        }

        const colors = [
            null,
            '#FF0D72',
            '#0DC2FF',
            '#0DFF72',
            '#F538FF',
            '#FF8E0D',
            '#FFE138',
            '#3877FF',
        ];

        const arena = createMatrix(12, 20);

        const player = {
            pos: {x: 0, y: 0},
            matrix: null,
            score: 0,
        };

        // 조작키 설정
        document.addEventListener('keydown', event => {
            if (event.keyCode === 37) { // 왼쪽 화살표
                playerMove(-1);
            } else if (event.keyCode === 39) { // 오른쪽 화살표
                playerMove(1);
            } else if (event.keyCode === 40) { // 아래 화살표
                playerDrop();
            } else if (event.keyCode === 81) { // Q (왼쪽 회전)
                playerRotate(-1);
            } else if (event.keyCode === 87) { // W (오른쪽 회전)
                playerRotate(1);
            } else if (event.keyCode === 38) { // 위쪽 화살표 (오른쪽 회전)
                playerRotate(1);
            }
        });

        playerReset();
        updateScore();
        update();
    </script>
</body>
</html>

확인

실습 1. 테트리스 만들기

원하는 기능을 말로 덧붙입니다

게임을 해 보면 아쉬운 점이 보입니다. 다음 블록이 무엇인지 알 수 없고, 휴대폰에는 키보드가 없어 조작할 수 없습니다. 코드를 직접 고치는 대신, 같은 대화창에서 말로 요청합니다.

두 번째 요청
이제 다음에 나올 블럭이 무엇인지 알려주는 미리보기 기능을 추가했으면 좋겠어. 또한 모바일로도 할 수 있게, 방향키를 시각화해줘.
이것이 피드백입니다실행해 보고 발견한 부족한 점을 말로 전달했습니다. 코드의 어느 줄을 고쳐야 하는지 몰라도, 무엇이 달라져야 하는지만 분명하면 AI가 코드를 고칩니다.

확인

실습 1. 테트리스 만들기

새 코드로 다시 확인합니다

  1. 새로 받은 코드를 4단계와 같은 방법으로 저장합니다. 이름을 tetris2.html로 하면 두 버전을 비교할 수 있습니다.
  2. 파일을 열어 오른쪽의 다음 블록 미리보기(NEXT)와 화면 아래 방향 버튼이 생겼는지 확인합니다.

예제 코드 2: 미리보기와 모바일 버튼이 추가된 테트리스

직접 진행하기 어려울 때

▶ 바로 플레이를 누르면 저장하지 않고 이 화면에서 바로 새 버전을 해 볼 수 있습니다.

tetris2.html
화면 아래 버튼을 누르거나, 게임 화면을 클릭한 뒤 키보드로 조작하세요.
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>테트리스 플러스</title>
    <style>
        body {
            background: #202028;
            color: #fff;
            font-family: 'Arial', sans-serif;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            height: 100vh;
            margin: 0;
            overflow: hidden;
            touch-action: manipulation;
        }
        .game-layout {
            display: flex;
            gap: 20px;
            align-items: flex-start;
        }
        #game-container {
            border: 4px solid #fff;
            box-shadow: 0 0 20px rgba(0,0,0,0.5);
        }
        .side-panel {
            display: flex;
            flex-direction: column;
            gap: 20px;
        }
        .info-box {
            background: #333;
            padding: 10px;
            border: 2px solid #555;
            text-align: center;
            min-width: 80px;
        }
        .info-label { font-size: 0.5em; color: #aaa; margin-bottom: 5px; }
        .info-value { font-size: 1em; font-weight: bold; }
        
        canvas { background-color: #000; display: block; }
        #nextCanvas { background-color: #111; }

        /* 모바일 컨트롤러 스타일 */
        .controls {
            margin-top: 20px;
            display: grid;
            grid-template-areas: 
                ". up ."
                "left down right";
            gap: 10px;
        }
        .btn {
            width: 60px;
            height: 60px;
            background: #444;
            border: none;
            border-radius: 10px;
            color: white;
            font-size: 24px;
            display: flex;
            align-items: center;
            justify-content: center;
            user-select: none;
            -webkit-tap-highlight-color: transparent;
        }
        .btn:active { background: #666; transform: scale(0.95); }
        .btn-up { grid-area: up; background: #555; }
        .btn-left { grid-area: left; }
        .btn-down { grid-area: down; }
        .btn-right { grid-area: right; }

        @media (max-width: 400px) {
            .game-layout { transform: scale(0.9); }
        }
    </style>
</head>
<body>

    <div class="game-layout">
        <div id="game-container">
            <canvas id="tetris" width="200" height="400"></canvas>
        </div>
        
        <div class="side-panel">
            <div class="info-box">
                <div class="info-label">NEXT</div>
                <canvas id="nextCanvas" width="80" height="80"></canvas>
            </div>
            <div class="info-box">
                <div class="info-label">SCORE</div>
                <div id="score" class="info-value">0</div>
            </div>
        </div>
    </div>

    <div class="controls">
        <div class="btn btn-up" onclick="playerRotate(1)">↻</div>
        <div class="btn btn-left" onclick="playerMove(-1)">←</div>
        <div class="btn btn-down" onclick="playerDrop()">↓</div>
        <div class="btn btn-right" onclick="playerMove(1)">→</div>
    </div>

    <script>
        const canvas = document.getElementById('tetris');
        const context = canvas.getContext('2d');
        const nextCanvas = document.getElementById('nextCanvas');
        const nextContext = nextCanvas.getContext('2d');
        const scoreElement = document.getElementById('score');

        context.scale(20, 20);
        nextContext.scale(20, 20);

        const colors = [null, '#FF0D72', '#0DC2FF', '#0DFF72', '#F538FF', '#FF8E0D', '#FFE138', '#3877FF'];

        function createPiece(type) {
            if (type === 'I') return [[0, 1, 0, 0], [0, 1, 0, 0], [0, 1, 0, 0], [0, 1, 0, 0]];
            else if (type === 'L') return [[0, 2, 0], [0, 2, 0], [0, 2, 2]];
            else if (type === 'J') return [[0, 3, 0], [0, 3, 0], [3, 3, 0]];
            else if (type === 'O') return [[4, 4], [4, 4]];
            else if (type === 'Z') return [[5, 5, 0], [0, 5, 5], [0, 0, 0]];
            else if (type === 'S') return [[0, 6, 6], [6, 6, 0], [0, 0, 0]];
            else if (type === 'T') return [[0, 7, 0], [7, 7, 7], [0, 0, 0]];
        }

        function drawMatrix(matrix, offset, ctx = context) {
            matrix.forEach((row, y) => {
                row.forEach((value, x) => {
                    if (value !== 0) {
                        ctx.fillStyle = colors[value];
                        ctx.fillRect(x + offset.x, y + offset.y, 1, 1);
                    }
                });
            });
        }

        function drawNext() {
            nextContext.fillStyle = '#111';
            nextContext.fillRect(0, 0, nextCanvas.width, nextCanvas.height);
            // 중앙 정렬 계산
            const xOff = (4 - player.next.length) / 2;
            const yOff = (4 - player.next.length) / 2;
            drawMatrix(player.next, {x: xOff, y: yOff}, nextContext);
        }

        function arenaSweep() {
            let rowCount = 1;
            outer: for (let y = arena.length - 1; y > 0; --y) {
                for (let x = 0; x < arena[y].length; ++x) {
                    if (arena[y][x] === 0) continue outer;
                }
                const row = arena.splice(y, 1)[0].fill(0);
                arena.unshift(row);
                ++y;
                player.score += rowCount * 10;
                rowCount *= 2;
            }
            updateScore();
        }

        function collide(arena, player) {
            const [m, o] = [player.matrix, player.pos];
            for (let y = 0; y < m.length; ++y) {
                for (let x = 0; x < m[y].length; ++x) {
                    if (m[y][x] !== 0 && (arena[y + o.y] && arena[y + o.y][x + o.x]) !== 0) return true;
                }
            }
            return false;
        }

        function createMatrix(w, h) {
            const matrix = [];
            while (h--) matrix.push(new Array(w).fill(0));
            return matrix;
        }

        function merge(arena, player) {
            player.matrix.forEach((row, y) => {
                row.forEach((value, x) => {
                    if (value !== 0) arena[y + player.pos.y][x + player.pos.x] = value;
                });
            });
        }

        function rotate(matrix, dir) {
            for (let y = 0; y < matrix.length; ++y) {
                for (let x = 0; x < y; ++x) [matrix[x][y], matrix[y][x]] = [matrix[y][x], matrix[x][y]];
            }
            if (dir > 0) matrix.forEach(row => row.reverse());
            else matrix.reverse();
        }

        function playerDrop() {
            player.pos.y++;
            if (collide(arena, player)) {
                player.pos.y--;
                merge(arena, player);
                playerReset();
                arenaSweep();
            }
            dropCounter = 0;
        }

        function playerMove(offset) {
            player.pos.x += offset;
            if (collide(arena, player)) player.pos.x -= offset;
        }

        function playerReset() {
            const pieces = 'TJLOSZI';
            if (player.next === null) {
                player.next = createPiece(pieces[pieces.length * Math.random() | 0]);
            }
            player.matrix = player.next;
            player.next = createPiece(pieces[pieces.length * Math.random() | 0]);
            player.pos.y = 0;
            player.pos.x = (arena[0].length / 2 | 0) - (player.matrix[0].length / 2 | 0);
            
            if (collide(arena, player)) {
                arena.forEach(row => row.fill(0));
                player.score = 0;
                updateScore();
            }
            drawNext();
        }

        function playerRotate(dir) {
            const pos = player.pos.x;
            let offset = 1;
            rotate(player.matrix, dir);
            while (collide(arena, player)) {
                player.pos.x += offset;
                offset = -(offset + (offset > 0 ? 1 : -1));
                if (offset > player.matrix[0].length) {
                    rotate(player.matrix, -dir);
                    player.pos.x = pos;
                    return;
                }
            }
        }

        function updateScore() {
            scoreElement.innerText = player.score;
        }

        let dropCounter = 0;
        let dropInterval = 1000;
        let lastTime = 0;

        function update(time = 0) {
            const deltaTime = time - lastTime;
            lastTime = time;
            dropCounter += deltaTime;
            if (dropCounter > dropInterval) playerDrop();
            
            context.fillStyle = '#000';
            context.fillRect(0, 0, canvas.width, canvas.height);
            drawMatrix(arena, {x: 0, y: 0});
            drawMatrix(player.matrix, player.pos);
            
            requestAnimationFrame(update);
        }

        const arena = createMatrix(10, 20);
        const player = {
            pos: {x: 0, y: 0},
            matrix: null,
            next: null,
            score: 0,
        };

        document.addEventListener('keydown', event => {
            if (event.keyCode === 37) playerMove(-1);
            else if (event.keyCode === 39) playerMove(1);
            else if (event.keyCode === 40) playerDrop();
            else if (event.keyCode === 38 || event.keyCode === 87) playerRotate(1);
        });

        playerReset();
        update();
    </script>
</body>
</html>
방금 한 일을 돌아보면말로 요구하고, 실행해서 확인하고, 다시 말로 피드백했습니다. 코드는 한 줄도 직접 쓰지 않았습니다. 다음 구간에서 이 방식의 이름과 원리를 정리합니다.

확인

바이브코딩 이해하기

방금 한 것이 바로 바이브코딩입니다

바이브코딩(Vibe Coding)은 사람이 자연어로 의도와 요구사항을 설명하면 생성형 AI가 코드를 작성하고, 사람은 실행 결과를 관찰하며 대화로 피드백해 결과물을 완성해 가는 소프트웨어 개발 방식입니다.

핵심은 "어떻게 만들 것인가(How)"를 AI에게 맡기고, 사람은 "무엇을 만들 것인가(What)"와 "제대로 되었는가(Check)"에 집중한다는 점입니다.

"코드가 존재한다는 사실조차 잊고, 흐름(vibe)에 완전히 몸을 맡기는 새로운 방식의 코딩"

안드레이 카파시(Andrej Karpathy), 2025년 2월 X 게시글의 취지를 우리말로 옮김
보고실행 결과를 본다
말하고원하는 것을 말한다
실행하고받은 코드를 돌린다
붙여 넣고오류는 그대로 AI에게

프로그래밍 문법을 몰라도, 원하는 결과를 분명하게 설명하고 판단할 수 있으면 소프트웨어를 만들 수 있습니다. 실습 1에서 여러분이 한 일이 정확히 이것입니다.

확인

바이브코딩 이해하기

짧은 게시글 하나가 1년 만에 올해의 단어가 되었습니다

시점을 눌러 보세요.

확인

바이브코딩 이해하기

바이브코딩은 네 단계를 반복하는 대화 루프입니다

단계를 누르면 설명과 실습 1의 예가 나옵니다. 루프 재생하기를 누르면 차례로 돌아갑니다.

원하는 결과가 나올 때까지 반복 단계를 눌러 보세요 1 · 사람목표 설명하기 2 · AI코드 생성 3 · 사람실행하고 관찰하기 4 · 사람피드백·개선
네 단계 중 세 단계가 사람의 몫입니다AI가 맡는 것은 코드 생성뿐입니다. 무엇을 만들지 정하고, 실행해서 판단하고, 방향을 다시 잡는 일은 모두 사람이 합니다.

확인

바이브코딩 이해하기

대화 루프가 모여 하나의 앱이 됩니다

대화 루프는 기능 하나를 다듬는 작은 반복입니다. 이 반복이 모여, 아이디어가 실제로 쓰이는 앱이 되기까지는 다섯 단계를 거칩니다. 오늘 연수는 파란 테두리의 세 단계까지 경험합니다.

1아이디어 구상

만들고 싶은 앱 전체를 높은 수준의 요청 하나로 설명합니다.

사람
2생성

AI가 화면, 동작 로직, 파일 구조를 포함한 첫 버전을 만듭니다.

AI
3반복 개선

직접 써 보며 후속 요청으로 기능을 더하거나 바꿉니다.

사람 AI
4테스트와 검증

보안, 품질, 정확성을 사람이 검토합니다. 학생에게 쓰기 전 교사가 반드시 거칠 단계입니다.

사람
5배포

다른 사람이 쓸 수 있도록 인터넷에 올리고 링크를 공유합니다. 방법은 다음 단계에서 자세히 다룹니다.

사람 AI

확인

바이브코딩 이해하기

만든 파일을 인터넷에 올려 링크로 나눕니다

내 컴퓨터에 저장한 HTML 파일은 나만 열 수 있습니다. 배포는 이 파일을 인터넷에 올려 누구나 접속할 수 있는 주소를 만드는 일입니다. Netlify를 쓰면 가입만 해 두고 파일을 끌어다 놓는 것만으로 몇 초 만에 배포할 수 있습니다.

1. Netlify에 가입하고 Drop 화면 열기

  1. 아래 버튼으로 Netlify Drop 페이지를 엽니다.
  2. 오른쪽 위에서 Sign up(가입)을 누르고, 구글 계정 등으로 가입해 로그인합니다.
로그인한 뒤에 올리세요. 로그인하지 않고 올리면 임시 사이트로 만들어져 일정 시간이 지나면 사라질 수 있습니다. 로그인한 상태에서 올려야 주소 이름을 바꾸고 계속 유지할 수 있습니다.

2. HTML 파일을 끌어다 놓기

로그인하면 아래와 같은 화면이 나옵니다. 저장해 둔 tetris.html 파일을 가운데 영역에 끌어다 놓으면 끝입니다. file을 눌러 파일을 직접 골라도 됩니다.

K
Drag & drop to
publish instantly
폴더, zip 파일, 또는 HTML 파일 하나를 놓으면 링크가 생깁니다
📄 tetris.html↓Drop hereor choose a file, folder1

3. 생긴 주소 확인하기

몇 초 뒤 프로젝트 화면으로 바뀝니다. 초록색으로 표시된 ○○○.netlify.app 주소를 누르면 게임이 열립니다. 이 주소를 복사해 누구에게나 보낼 수 있습니다.

Project overviewProject configuration3Deploys4Domain management
🌐 Public
● brave-tetris-1234.netlify.app2
Last deployed from Netlify Drop
Project configurationShare
번호무엇인가요
2배포된 주소입니다. 처음에는 무작위 이름이 붙습니다. 누르면 새 탭에서 결과물이 열립니다.
3Project configuration에서 프로젝트 이름을 바꾸면 주소가 내가정한이름.netlify.app으로 바뀝니다. 영어 소문자, 숫자, 하이픈(-)만 쓸 수 있습니다.
4Deploys는 고친 파일을 다시 올리는 곳입니다. 여기에 새 파일을 끌어다 놓으면 주소는 그대로, 내용만 바뀝니다.
수정본은 Deploys에 올리세요. 처음 Drop 화면에 다시 올리면 주소가 다른 새 사이트가 하나 더 생깁니다. 이미 나눠 준 링크를 그대로 쓰려면 기존 프로젝트의 Deploys에 올려야 합니다.
공개(Public) 주소입니다. 링크를 아는 사람은 누구나 볼 수 있습니다. 학생 이름이나 성적 같은 개인정보가 담긴 파일은 올리지 마세요.
왜 배포까지 알아야 할까요?수업 도구는 학생이 열 수 있어야 쓸모가 있습니다. 링크 하나를 클래스룸이나 QR 코드로 나눠 주면, 학생은 설치 없이 휴대폰이나 크롬북에서 바로 사용할 수 있습니다.

확인

바이브코딩 이해하기

전통적 프로그래밍과 무엇이 다를까요

구분전통적 프로그래밍바이브코딩
입력 방식프로그래밍 언어로 한 줄씩 작성자연어로 의도와 요구사항을 설명
필요한 역량언어 문법, 라이브러리, 개발 도구 지식문제를 정의하고 설명하는 힘, 결과를 판단하는 힘
반복 속도시간에서 며칠 단위분에서 시간 단위
코드 이해 수준한 줄 한 줄 깊이 이해결과 중심, 코드는 얕게 이해하거나 읽지 않음
잘 맞는 일실제 서비스, 복잡한 로직, 장기 유지보수시제품, 수업용 도구, 개인 프로젝트, 아이디어 검증
사람의 역할코드를 직접 작성하는 사람요구사항을 설계하고 결과를 검증하는 사람

같은 AI 코딩이라도 태도에 따라 나뉩니다

위치를 눌러 비교해 보세요.

사람이 코드를 직접 통제AI에게 완전히 맡김

확인

바이브코딩 이해하기

사람의 역할은 사라지지 않고 바뀝니다

이전코드를 작성하는 사람

문법에 맞게 한 줄씩 코드를 쓰고, 오류를 직접 찾아 고칩니다.

바이브코딩설계하고 검증하는 사람

무엇을 만들지 분명히 정의하고, 결과가 의도대로인지 판단해 방향을 잡습니다.

기술 교과의 문제해결 과정과 닮았습니다문제를 확인하고, 해결책을 구상하고, 만들어 보고, 평가해 다시 개선하는 흐름이 바이브코딩의 대화 루프와 같습니다. 코드는 AI가 쓰지만 문제를 정의하고 평가하는 사고는 사람의 몫입니다.
피드백을 잘 주는 요령한 번에 하나씩 요청하기, "안 돼요" 대신 본 현상을 그대로 설명하기, 오류 메시지는 그대로 붙여 넣기, 수정한 전체 코드를 다시 달라고 하기.

꼭 알아야 할 한계

코드를 모르면 고치기 어렵습니다AI가 버그를 해결하지 못하면 원인을 찾기 어렵고, 시간이 지나 기능을 바꾸기도 힘듭니다.
보안과 정확성은 보장되지 않습니다그럴듯하게 동작해도 보안 취약점이나 계산 오류가 숨어 있을 수 있습니다.
개인정보를 넣지 마세요요청문에 학생 이름이나 성적 같은 개인정보를 넣으면 외부 AI 서비스로 전송됩니다.
일회성, 소규모 용도에 적합합니다수업 한 차시에 쓰는 도구나 시제품에는 강력하지만, 오래 쓸 서비스라면 사람의 검토가 필요합니다.
참고: Andrej Karpathy, X 게시글(2025.02) · Google Cloud, "What is vibe coding?" · Martin Fowler, "Vibe Coding"(2026) · Simon Willison, "Not all AI-assisted programming is vibe coding"(2025) · Collins Dictionary, Word of the Year 2025 · Jonas Kim, "프롬프트에서 하네스까지"(2026)

확인

실습 2. 제미나이 캔버스

저장하지 않고 바로 실행할 수 있습니다

실습 1에서는 코드를 복사하고, 메모장에 붙여 넣고, 파일로 저장한 뒤 열어야 했습니다. 제미나이의 캔버스를 켜면 코드와 실행 화면이 대화창 옆에 함께 열려, 이 과정이 모두 사라집니다.

한 번 수정할 때 거치는 과정

파일 저장 방식 6단계

  • 1대화창에 요청하기
  • 2코드 복사하기
  • 3메모장에 붙여 넣기
  • 4HTML 파일로 저장하기
  • 5파일 열어 실행하기
  • 6대화창으로 돌아가 다시 요청하기

캔버스 방식 2단계

  • 1대화창에 요청하기
  • 2옆 화면에서 바로 실행하고 다시 요청하기
파일 저장부터 해 본 이유 캔버스는 편리하지만 과정이 가려져 있습니다. AI가 만든 것이 결국 하나의 HTML 파일이고, 그 파일을 브라우저가 실행한다는 구조를 직접 겪어 보면 캔버스 안에서 무슨 일이 일어나는지 이해할 수 있습니다.

확인

실습 2. 제미나이 캔버스

캔버스를 켜고 바로 실행해 봅니다

캔버스는 여기에 있습니다

입력창 왼쪽의 + 버튼 → 도구 더보기 → Canvas 순서로 누릅니다.

무엇을 도와드릴까요?
+1Gemini에게 물어보기Flash ⌄
파일 업로드
Drive에서 파일 추가
업로드 더보기 ›

이미지 만들기
동영상 만들기
음악 만들기
도구 더보기 ›2
Canvas3
Deep Research
가이드 학습
개인 인텔리전스

제미나이 화면은 업데이트에 따라 메뉴 이름과 위치가 조금씩 바뀔 수 있습니다. 입력창 주변의 도구 관련 메뉴에서 Canvas를 찾으세요.

캔버스로 만들기

  1. 캔버스 켜기: 위 순서로 Canvas를 누르면 입력창에 캔버스가 켜졌다는 표시가 생깁니다.
  2. 요청하기: 아래 요청문을 보내면 화면 오른쪽에 캔버스가 열리고 코드가 작성됩니다.
  3. 미리보기로 실행하기: 캔버스 위쪽의 미리보기(Preview)를 누르면 그 자리에서 실행됩니다.
  4. 대화로 고치기: 고칠 점을 채팅창에 말하면 캔버스의 내용이 바로 수정됩니다.
  5. 공유하기: 완성되면 공유 기능으로 링크를 만들어 다른 사람에게 보여 줄 수 있습니다.
캔버스에서 보내 볼 요청
바로 실행할 수 있는 테트리스 게임을 만들어줘!
요청이 짧아진 이유실습 1에서는 파일로 저장해 실행해야 해서 "HTML 형식으로"를 꼭 말해야 했습니다. 캔버스는 실행 가능한 형태로 알아서 만들고 바로 보여 주기 때문에, 형식을 말하지 않고 무엇을 만들지만 말해도 됩니다.

확인

실습 3. 나만의 게임

나만의 게임 요청문을 설계합니다

왼쪽에서 원하는 게임을 고르면 오른쪽에 요청문이 만들어집니다. 좋은 요청문은 무엇을, 어떻게 조작하고, 어떤 규칙과 기능이 있는지를 담고 있습니다.

1. 게임 종류
2. 조작 방법 (여러 개 선택)
3. 넣고 싶은 기능 (여러 개 선택)
4. 화면 분위기
완성된 요청문

복사한 요청문을 캔버스를 켠 제미나이에 붙여 넣으세요. 그대로 써도 되고, 원하는 규칙을 더 적어도 좋습니다.

확인

실습 3. 나만의 게임

실행하고, 말로 다듬어 완성합니다

첫 결과가 나오면 미리보기로 실행해 보고, 대화 루프를 두 번 이상 돌려 게임을 다듬어 보세요.

이럴 땐 이렇게 말해 보세요

이런 상황이면이렇게 요청
게임이 아예 안 움직여요"게임이 시작되지 않아. 화면에 아무 변화가 없어. 고쳐줘."
이상하게 움직여요"캐릭터가 벽을 통과해. 벽에 닿으면 멈추게 고쳐줘."처럼 본 현상을 그대로 설명
너무 어렵거나 쉬워요"처음 속도를 절반으로 줄이고, 30초마다 조금씩 빨라지게 해줘."
오류 메시지가 보여요메시지를 그대로 복사해 붙여 넣고 "이 오류를 고쳐줘."
완성하면 공유해 보세요. 캔버스의 공유 기능으로 링크를 만들어 옆 선생님과 서로의 게임을 해 보세요.

확인

교육 현장 활용

교사에게 바이브코딩은 "필요한 도구를 직접 만드는 힘"입니다

딱 맞는 수업 도구를 찾지 못해 PPT와 활동지로 대신하던 일들을, 이제 말로 설명해 직접 만들 수 있습니다. 교육 현장에서는 크게 세 영역에서 활용할 수 있습니다.

수업 자료보고, 누르고, 움직이는 수업

교과서의 그림을 학생이 직접 조작하는 시뮬레이션으로 바꿉니다.

  • 개념 시뮬레이션 (궤도, 그래프, 회로)
  • 인터랙티브 학습지와 퀴즈
  • 게임형 복습 활동
학생 활동학생이 만드는 사람이 되는 수업

학생이 문제를 정의하고, 요구사항을 설명하고, 결과를 평가하며 산출물을 만듭니다.

  • 문제해결 프로젝트의 시제품 만들기
  • 요구사항을 글로 명세하는 연습
  • 만든 결과를 서로 사용해 보고 평가하기
학교 업무반복 업무를 줄이는 도구

매번 규정을 찾아보고 계산하던 일을 한 번에 처리하는 도구를 만듭니다.

  • 규정 기반 계산기와 실무 가이드
  • 문서 양식 자동 작성
  • 체크리스트와 안내 페이지

만든 것을 나누는 세 가지 방법

방법특징
제미나이 공유 링크캔버스에서 만든 결과를 공유 버튼으로 바로 링크로 만듭니다. 가장 간단합니다.
Claude 아티팩트Claude에서 만든 결과물을 공개 링크로 공유합니다.
Netlify 배포HTML 파일을 끌어다 놓아 나만의 웹 주소를 만듭니다. 원하는 이름을 붙이고 계속 고쳐 올릴 수 있습니다.
수업에 쓰기 전에 확인하세요. 교사가 먼저 끝까지 사용해 보며 내용의 정확성을 검증합니다. 학생 이름이나 성적 같은 개인정보는 넣지 않습니다. 학생이 생성형 AI를 직접 쓰게 할 때는 서비스의 이용 연령 기준과 학교 지침을 확인합니다.

확인

교육 현장 활용

바이브코딩으로 만든 활용 사례

모두 바이브코딩으로 만든 결과물입니다. 카드를 눌러 직접 사용해 보고, 어떤 요소가 들어 있는지 살펴보세요.

사례를 볼 때 살펴볼 점어떤 문제를 해결하려고 만들었는지, 학생이나 교사가 무엇을 누르고 조작하는지, 어떤 요소(버튼, 그래프, 소리 등)가 쓰였는지 보세요. 다음 단계에서 이런 요소들을 정리합니다.

확인

교육 현장 활용

어떤 요소를 넣을 수 있을까요?

요소를 누르면 설명, 수업 활용 예, 요청문 예시가 나옵니다. 일부 요소는 바로 체험해 볼 수 있습니다.

쉬움 요청 한 번으로 보통 몇 번의 피드백으로 심화 구글 시트, 앱스크립트, AI API 같은 외부 연결 필요

확인

마무리

오늘 경험한 바이브코딩

코드를 쓰는 대신 원하는 것을 말하고, 결과를 확인하고, 다시 말하며 게임을 완성했습니다. 이 방식은 게임뿐 아니라 퀴즈, 타이머, 모둠 뽑기처럼 수업에 필요한 작은 도구를 만드는 데도 그대로 쓸 수 있습니다.

AI 공부 자료

실습 도구

나의 진행 현황