AI를 활용한 개인 블로그 만들기 (1)
1. 개발 동기 최근 NAS를 구축하며 요즘 NAS는 단순한 저장 공간으로만 활용되는 것이 아닌 다양한 서버로 활용도 가능하다는 것을 알게 되었다. 어렸을 때부터 게임과 웹을 개발하며 개인 서버에 대한 로망이 있었기 때문에, 이 기회에 함께 구축해 보면 좋겠다고 생각했다.
기존에 티스토리 블로그 하나를 개설해 뒀었는데 환경이 익숙하지 않아 못 다루는 기능이 많고 불편했다. 요즘 웹 클라이언트는 사실상 AI가 전문가급의 퍼포먼스를 보여주기 때문에 개발에 있어 예전만큼 부담도 없겠다, 웹 서버를 구축하며 아예 기존 블로그를 리뉴얼하고 호스팅도 내 서버에서 직접 해보기로 했다.
2. 프롬프트 설계하기 양질의 결과물을 뽑아내기 위해선 AI에게 디테일한 명령을 내릴 필요가 있다. 이것이 프롬프트다. 하지만 재미있게도, 이젠 AI에게 어떻게 명령을 내릴지도 AI가 답해준다.
우선, 챗 지피티와 제미나이를 통해 블로그를 짤 프롬프트를 만들었다. 참고로 이 때 필자는 제미나이와 챗 지피티 둘 다 무료 버젼을 사용하고 있었다.
prompt기존에 티스토리 플랫폼을 이용하던 내 개인 블로그를 이번에 구축한 개인 홈 nas 겸 웹서버에 이전하려해. 내 디자인 작업물과 개발 작업물, 게임 스토리 기획, 공부한 내용을 정리할 수 있는 깔끔하고 세련된 디자인과 UI 를 갖춘 웹 페이지를 만들기 좋은 프롬프트를 바로 복사 붙여넣기 할 수 있도록 구성해줘.
입력한 결과, 각각 해당 결과값을 얻을 수 있었다.
제미나이 (3.5 flash - extended)
prompt[역할 부여] 당신은 트렌디하고 세련된 UI/UX 감각을 가진 '시니어 프론트엔드 개발자 겸 웹 디자이너'입니다. 개인 홈 서버(NAS)에 직접 호스팅할 저만의 독립적인 블로그 및 포트폴리오 웹사이트를 개발하려고 합니다. [프로젝트 개요] - 목적: 디자인 작업물, 개발 프로젝트, 게임 스토리 기획, 공부한 내용을 아카이빙하고 공유하는 웹사이트 - 디자인 무드: 미니멀, 깔끔함, 세련됨 (애플이나 노션처럼 여백을 잘 활용한 타이포그래피 중심의 디자인) - 테마: 라이트/다크 모드 완벽 지원 및 전환 토글 포함 [카테고리별 맞춤 UI 요구사항] 웹사이트는 다음 4개의 카테고리로 구성되며, 각 콘텐츠의 성격에 맞는 최적화된 레이아웃을 적용해 주세요. 1. Design (디자인 작업물) - 고해상도 그래픽 작업물이 돋보일 수 있는 핀터레스트 스타일의 Masonry 그리드 UI - 썸네일에 마우스 호버 시 부드러운 이미지 줌 인(Scale) 효과와 프로젝트 제목 오버레이 적용 2. Dev (개발 프로젝트) - 썸네일, 프로젝트명, 간단한 설명, 사용된 기술 스택(태그 칩 형태)이 포함된 모던한 카드 레이아웃 - 각 카드 내에 GitHub 링크 및 Live Demo 버튼 배치 3. Story (게임 스토리 기획) - 긴 글에 몰입하기 좋은 브런치(Brunch) 또는 미디엄(Medium) 스타일의 읽기 편안한 텍스트 중심 레이아웃 - 가독성이 뛰어난 폰트와 넉넉한 줄간격(Line-height) 적용 - 세계관, 챕터, 에피소드 단위로 묶어서 볼 수 있는 목차(TOC) 또는 아코디언 UI 추가 4. Study (공부 기록 및 기술 블로그) - 노션(Notion) 스타일의 깔끔한 리스트형 블로그 UI - 글 제목, 카테고리, 발행일, 요약문 표시 - 마크다운(Markdown) 렌더링을 고려하여, 코드 블록(Syntax Highlighting)과 인용구 디자인이 세련되게 보이도록 CSS 세팅 [기술 스택 및 구현 조건] - 프레임워크: Next.js와 Tailwind CSS 조합 (또는 정적 사이트 생성을 위한 Astro와 Tailwind CSS) - 아이콘: Lucide Icons 또는 Heroicons를 사용하여 미니멀하게 구성 - 반응형 웹: 모바일, 태블릿, 데스크톱 환경에 부드럽게 대응 (Mobile-First) - 홈 서버에서 쾌적하게 구동될 수 있도록 가볍고 시맨틱한 마크업 작성 [출력 요청사항] 1. 먼저 전체 웹사이트의 첫인상이 될 '메인 홈 화면(Hero 섹션 및 최신 글/작업물 요약)'의 전체 코드를 작성해 주세요. 2. CSS와 Tailwind 유틸리티 클래스를 세밀하게 조정하여, 별도의 디자인 수정 없이도 즉시 사용할 수 있는 수준의 고퀄리티 컴포넌트를 만들어 주세요.
챗 지피티 (스탠다드)
prompt당신은 세계적인 수준의 UI/UX 디자이너이자 시니어 풀스택 개발자다. 나는 기존 티스토리 기반 개인 블로그를 폐기하고, 직접 구축한 개인 홈 NAS + 웹서버 환경으로 이전하려고 한다. 이번 프로젝트의 목표는 단순한 블로그가 아니라 아래 요소들을 모두 통합하는 “개인 디지털 아카이브 플랫폼”을 만드는 것이다. - 디자인 포트폴리오 - 개발 프로젝트 기록 - 게임 스토리 및 세계관 기획 - 공부 및 리서치 노트 - 기술 실험 기록 - 개인 블로그 아카이브 사이트는 “개발자 + 디자이너 + 크리에이터의 개인 연구실” 같은 분위기를 가져야 한다. ================================================== [핵심 디자인 방향] ================================================== 다음 감성을 중심으로 디자인할 것: - 미니멀 - 미래지향적 - 차분한 다크 테마 - 몰입감 있는 레이아웃 - Apple / Linear / Notion / Vercel 감성 - 감성적이지만 엔지니어링 느낌 존재 - 긴 글 읽기 편한 구조 - 작업물을 고급스럽게 보여주는 UI - 디지털 아카이브 느낌 - 연구실/랩/워크스페이스 분위기 절대 피할 것: - 티스토리 스킨 느낌 - 올드한 블로그 디자인 - 과한 네온 사이버펑크 - 복잡한 사이드바 - 정신없는 애니메이션 - 광고 느낌 UI - 지나친 색상 사용 ================================================== [컬러 스타일] ================================================== 기본 테마: - Dark Theme 배경 컬러: - #0B0D10 - #111318 포인트 컬러: - Soft Blue - Cyan - Purple Accent 스타일: - 눈이 편안한 대비 - 은은한 glow - 부드러운 그림자 - glassmorphism 약하게 사용 가능 ================================================== [타이포그래피] ================================================== - Pretendard 또는 Inter 기반 - 제목은 강한 대비 - 본문은 긴 글 읽기 최적화 - 코드 블록 전용 폰트 적용 - 텍스트 중심 UX 중요 ================================================== [UI / UX 요구사항] ================================================== - 카드 기반 레이아웃 - 충분한 여백 - 12~20px radius - 부드러운 hover transition - Framer Motion 기반 자연스러운 애니메이션 - 페이지 전환 애니메이션 - 스크롤 인터랙션 - 모바일 반응형 완벽 지원 - 다크모드 최적화 - 코드 하이라이팅 강화 - TOC(Table of Contents) - 검색 기능 - 태그 시스템 - 시리즈 포스트 연결 - 이전글/다음글 네비게이션 ================================================== [사이트 구조] ================================================== 1. Home 포함 요소: - Hero Section - 짧은 소개 문구 - 대표 프로젝트 - 최근 글 - 현재 진행 중인 작업 - 기술 스택 - GitHub 링크 - Contact 섹션 - 자연스러운 스크롤 인터랙션 2. Portfolio 카테고리: - Design - Development - Game Narrative - Research 프로젝트 카드 구성: - 썸네일 - 설명 - 사용 기술 - GitHub 링크 - 제작 기간 - 상세 페이지 이동 3. Blog 필수 기능: - Markdown/MDX 기반 - 검색 - 태그 - 카테고리 - 코드 하이라이팅 - TOC - 시리즈 구조 - SEO 최적화 - RSS/Sitemap 지원 4. Game Story Archive 분위기: - 위키 + 아트북 + 개발노트 느낌 포함 기능: - 세계관 정리 - 인물 관계도 - 지역 설정 - 타임라인 - 설정 문서 열람 UI 5. Research Notes Notion 스타일 기반: - 문서 중심 UI - 내부 링크 시스템 - 접을 수 있는 섹션 - 코드 블록 - 수식 지원 - 마크다운 기반 ================================================== [기술 스택] ================================================== Frontend: - Next.js - TypeScript - TailwindCSS - Framer Motion - shadcn/ui 콘텐츠 관리: - MDX 기반 블로그 - Git 기반 콘텐츠 관리 배포 환경: - 개인 NAS - Docker 기반 운영 - nginx reverse proxy - HTTPS - 캐싱 전략 - 이미지 최적화 ================================================== [구현 요구사항] ================================================== 반드시 아래 결과물을 모두 생성할 것: 1. 전체 사이트 아키텍처 2. 페이지별 와이어프레임 설명 3. 디자인 시스템 정의 4. UI 컴포넌트 구조 5. 폴더 구조 6. Next.js App Router 구조 7. 예시 페이지 코드 8. 반응형 설계 전략 9. Docker 구성 예시 10. nginx 설정 예시 11. SEO 전략 12. 콘텐츠 관리 전략 13. NAS 운영 전략 14. 성능 최적화 전략 15. 접근성 고려사항 ================================================== [디자인 레퍼런스] ================================================== 다음 사이트들의 장점을 참고하되 절대 복제하지 말 것: - https://vercel.com - https://linear.app - https://notion.so - https://raycast.com - https://supabase.com ================================================== [추가 요청] ================================================== 마지막에는 반드시 아래 항목까지 생성할 것: - 사이트 이름 후보 10개 - 슬로건 후보 10개 - 다크 테마 컬러 팔레트 - 추천 폰트 조합 - 추천 아이콘 스타일 - 추천 오픈소스 라이브러리 - 추천 블로그 콘텐츠 구조 - 추천 NAS 폴더 구조 - 추천 Docker 구성 전략 ================================================== [출력 스타일] ================================================== - 매우 구체적으로 작성 - 실제 프로덕션 수준으로 설계 - 코드 품질 높게 유지 - 디자인 이유까지 설명 - UI/UX 의도 설명 - 유지보수성 고려 - 확장성 고려 - 콘텐츠가 많아져도 구조가 무너지지 않게 설계 - 개발자 포트폴리오 + 디지털 연구실 + 창작 아카이브 분위기 유지 최종 결과물은 실제 배포 가능한 수준의 현대적인 개인 웹 플랫폼이어야 한다.
3. 메인 페이지 만들기 개인적으로 두 AI의 프롬프트 중 지피티의 프롬프트가 더 디테일하고 마음에 들었다. 따라서 지피티의 프롬프트를 사용하되, 핵심 테마 컬러와 상세 무드등을 조금씩 바꾸어 제미나이에 입력하였다. 그러자 블로그의 구조에 대해 상세하게 설명이 나왔는데, 사실 필자에게 필요한 것은 이런 이론적인 내용이 아닌 눈에 보이는 결과물이었다. 또 클라이언트가 완성되면 호스팅은 추후 진행할 계획이었기에, 우선 클라이언트 UI/UX를 코딩해 달라고 부탁했다. 결과는 다음과 같았다.
TypeScript'use client'; import React, { useState } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { Search, Terminal, Calendar, Clock, ArrowUpRight, Hash } from 'lucide-react'; // 1. Mock Data (실제 마크다운/MDX 연동 전 테스트 데이터) const CATEGORIES = ['All', 'Engineering', 'Design', 'Narrative', 'Research']; const MOCK_POSTS = [ { id: 1, title: "Next.js App Router 환경에서의 다크모드 그라데이션 최적화 기법", description: "홈 서버의 대역폭 한계를 극복하고 CSS 렌더링 블로킹을 최소화하기 위한 은은한 Glow 레이어 및 Glassmorphism 최적화 가이드입니다.", category: "Engineering", tags: ["Next.js", "TailwindCSS", "Performance"], date: "2026-05-20", readingTime: "5 min read", isFeatured: true, }, { id: 2, title: "Linear와 Vercel의 디자인 시스템 분석: 미니멀리즘과 엔지니어링의 결합", description: "과도한 네온 컬러를 배제하고 가독성을 극대화하는 다크 테마 컬러 팔레트 구축 및 타이포그래피 정렬 방식을 심층 분석합니다.", category: "Design", tags: ["DesignSystem", "UIUX", "DarkTheme"], date: "2026-05-18", readingTime: "8 min read", isFeatured: false, }, { id: 3, title: "스페이스 오페라 장르에서의 양자 도약 세계관 및 인물 관계도 설계 노하우", description: "위키 형태의 아카이브 구축을 위한 하이퍼링크 기반 설정 문서화 방법과 대서사시의 시간선 흐름을 제어하는 프레임워크.", category: "Narrative", tags: ["WorldBuilding", "GameDev", "Writing"], date: "2026-05-15", readingTime: "12 min read", isFeatured: false, }, { id: 4, title: "i5-8400 기반 홈 서버에서 Docker 컨테이너 리소스 격리 및 모니터링", description: "RAID 1 볼륨의 디스크 I/O 병목을 방지하고 Nginx 리버스 프록시 캐싱 전략을 통한 응답 속도 향상 실험 기록.", category: "Research", tags: ["NAS", "Docker", "Nginx"], date: "2026-05-10", readingTime: "6 min read", isFeatured: false, }, ]; export default function BlogPage() { const [activeCategory, setActiveCategory] = useState('All'); const [searchQuery, setSearchQuery] = useState(''); // 필터링 로직 const filteredPosts = MOCK_POSTS.filter(post => { const matchesCategory = activeCategory === 'All' || post.category === activeCategory; const matchesSearch = post.title.toLowerCase().includes(searchQuery.toLowerCase()) || post.description.toLowerCase().includes(searchQuery.toLowerCase()) || post.tags.some(tag => tag.toLowerCase().includes(searchQuery.toLowerCase())); return matchesCategory && matchesSearch; }); return ( <div className="min-h-screen bg-[#0B0D10] text-[#EDEDED] font-sans antialiased selection:bg-[#38BDF8]/30"> {/* Background Subtle Glow Line */} <div className="absolute top-0 left-0 right-0 h-[1px] bg-gradient-to-r from-transparent via-[#26292F] to-transparent" /> <main className="max-w-6xl mx-auto px-6 py-16 space-y-12 relative z-10"> {/* Header Section */} <div className="flex flex-col md:flex-row md:items-end justify-between border-b border-[#1A1D23] pb-8 gap-6"> <div className="space-y-2"> <div className="flex items-center gap-2 text-xs font-mono text-[#38BDF8]"> <Terminal size={14} /> <span>LOG_SYSTEM_ACTIVE</span> </div> <h1 className="text-3xl font-bold tracking-tight text-[#EDEDED]">Research & Archives</h1> <p className="text-[#A0A0A0] max-w-md text-sm leading-relaxed"> 디지털 실험 기록, 개발 로그, 디자인 시스템 및 내러티브 기획을 보존하는 독립 연구실 공간입니다. </p> </div> {/* Lab Metadata Display */} <div className="flex gap-6 text-xs font-mono text-[#A0A0A0] bg-[#111318] border border-[#26292F] px-4 py-3 rounded-xl"> <div> <span className="text-[#64748B]">TOTAL_LOGS:</span> <span className="text-[#EDEDED]">{MOCK_POSTS.length}</span> </div> <div className="hidden sm:block border-l border-[#26292F] h-4" /> <div className="hidden sm:block"> <span className="text-[#64748B]">LAST_INDEXED:</span> <span className="text-[#EDEDED]">2026.05.20</span> </div> </div> </div> {/* Search & Filter Bar (Linear / Raycast Style) */} <div className="flex flex-col sm:flex-row gap-4 items-center justify-between"> {/* Category Tabs */} <div className="flex items-center gap-1.5 p-1 bg-[#111318] border border-[#26292F] rounded-xl overflow-x-auto w-full sm:w-auto"> {CATEGORIES.map((category) => ( <button key={category} onClick={() => setActiveCategory(category)} className={`px-3 py-1.5 text-xs font-medium rounded-lg transition-all duration-200 whitespace-nowrap ${ activeCategory === category ? 'bg-[#26292F] text-[#38BDF8] shadow-sm' : 'text-[#A0A0A0] hover:text-[#EDEDED]' }`} > {category} </button> ))} </div> {/* Search Input */} <div className="relative w-full sm:w-72 group"> <Search className="absolute left-3.5 top-1/2 -translate-y-1/2 text-[#64748B] group-focus-within:text-[#38BDF8] transition-colors duration-200" size={16} /> <input type="text" placeholder="Search archives..." value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} className="w-full bg-[#111318] border border-[#26292F] focus:border-[#38BDF8]/50 focus:outline-none rounded-xl pl-10 pr-4 py-2 text-xs text-[#EDEDED] placeholder-[#64748B] transition-all duration-300 focus:shadow-[0_0_20px_rgba(56,189,248,0.05)]" /> </div> </div> {/* Posts Grid Container */} <div className="space-y-8"> <AnimatePresence mode="popLayout"> {filteredPosts.map((post, index) => { // Featured Post Layout (Bento 스타일로 최상단에 넓게 배치) if (post.isFeatured && activeCategory === 'All' && searchQuery === '') { return ( <motion.article layout initial={{ opacity: 0, y: 15 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, scale: 0.98 }} transition={{ duration: 0.4 }} key={post.id} className="relative group bg-[#111318] border border-[#26292F] hover:border-[#38BDF8]/30 rounded-2xl p-6 md:p-8 flex flex-col justify-between gap-6 transition-all duration-300 cursor-pointer overflow-hidden shadow-2xl col-span-2" > {/* Background Subtle Gradient Shadow */} <div className="absolute -right-20 -top-20 w-64 h-64 bg-gradient-to-br from-[#38BDF8]/5 to-[#A855F7]/0 rounded-full blur-3xl opacity-0 group-hover:opacity-100 transition-opacity duration-500" /> <div className="space-y-4 relative z-10"> <div className="flex items-center gap-3 text-xs font-mono text-[#A0A0A0]"> <span className="text-[#38BDF8] bg-[#38BDF8]/10 px-2 py-0.5 rounded-md font-sans font-medium">{post.category}</span> <span className="flex items-center gap-1"><Calendar size={12}/>{post.date}</span> <span className="flex items-center gap-1"><Clock size={12}/>{post.readingTime}</span> </div> <h2 className="text-xl md:text-2xl font-bold tracking-tight text-[#EDEDED] group-hover:text-[#38BDF8] transition-colors duration-200 flex items-start justify-between gap-4"> <span className="leading-snug">{post.title}</span> <ArrowUpRight className="text-[#64748B] group-hover:text-[#38BDF8] group-hover:translate-x-0.5 group-hover:-translate-y-0.5 transition-all duration-200 shrink-0 mt-1" size={20} /> </h2> <p className="text-sm text-[#A0A0A0] leading-relaxed max-w-3xl"> {post.description} </p> </div> <div className="flex flex-wrap gap-1.5 pt-4 border-t border-[#1A1D23] relative z-10"> {post.tags.map(tag => ( <span key={tag} className="inline-flex items-center gap-0.5 text-xs font-mono text-[#64748B] bg-[#0B0D10] border border-[#1A1D23] px-2 py-1 rounded-lg"> <Hash size={10} />{tag} </span> ))} </div> </motion.article> ); } // Standard Post Layout (일반 리스트 카드 형태) return ( <motion.article layout initial={{ opacity: 0, y: 15 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, scale: 0.98 }} transition={{ duration: 0.3, delay: index * 0.05 }} key={post.id} className="group bg-[#111318]/60 border border-[#26292F] hover:border-[#A855F7]/30 rounded-xl p-5 flex flex-col justify-between gap-4 transition-all duration-300 cursor-pointer relative overflow-hidden" > <div className="space-y-2.5"> <div className="flex items-center gap-3 text-xs font-mono text-[#A0A0A0]"> <span className="text-[#A855F7] bg-[#A855F7]/10 px-2 py-0.5 rounded-md font-sans font-medium">{post.category}</span> <span className="flex items-center gap-1"><Calendar size={12}/>{post.date}</span> <span className="flex items-center gap-1"><Clock size={12}/>{post.readingTime}</span> </div> <h3 className="text-base font-semibold tracking-tight text-[#EDEDED] group-hover:text-[#A855F7] transition-colors duration-200 flex items-center justify-between gap-2"> <span>{post.title}</span> <ArrowUpRight className="text-[#475569] group-hover:text-[#A855F7] transition-colors duration-200 shrink-0" size={16} /> </h3> <p className="text-xs text-[#A0A0A0] leading-relaxed line-clamp-2"> {post.description} </p> </div> <div className="flex flex-wrap gap-1 pt-2 border-t border-[#1A1D23]/50"> {post.tags.map(tag => ( <span key={tag} className="inline-flex items-center gap-0.5 text-[10px] font-mono text-[#64748B] px-1.5 py-0.5"> #{tag} </span> ))} </div> </motion.article> ); })} </AnimatePresence> {/* Empty State */} {filteredPosts.length === 0 && ( <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} className="text-center py-20 border border-dashed border-[#26292F] rounded-2xl bg-[#111318]/20" > <Terminal className="mx-auto text-[#64748B] mb-3" size={24} /> <p className="text-sm font-mono text-[#64748B]">NO_ARCHIVES_FOUND_MATCHING_THE_CRITERIA</p> </motion.div> )} </div> </main> </div> ); }
소스를 받긴 했는데, 필자는 10년 전 HTML, CSS, JavaScript, PHP를 활용해 가내수공업마냥 한땀한땀 코딩하던 세대인지라 이 tsx 확장자가 뭔지, 어떻게 열어야 하는지 몰랐다. 따라서 이 부분도 제미나이에게 물어보았다.
간단하게 정리하자면, tsx 파일은 HTML과 CSS, JavaScript를 합친 파일이라고 한다. 우리가 앞서 확인한 소스의 return 구문 안쪽 내용이 HTML 소스이고, HTML 태그 안에 CSS가 들어가 있다. (이렇게 HTML 태그 안에 CSS를 작성하는 방식을 TailwindCSS라고 하는데, 그나마 필자가 거의 유일하게 알고 있던 내용이었다.) 또한, 자바스크립트에 '타입(Type)' 이라는 언어 규칙을 추가해 브라우저를 켜보기 전에 코드 에디터에서 미리 경고를 띄워준다고 한다. 옛날 웹 코딩을 하며 가장 불편했던 점이 에디터에서 오류를 잘 잡아주지 않아 직접 웹 페이지에 들어가 확인하고, F12 키를 눌러 하나하나 수정해 보며 오류를 고쳤던 것을 생각하면 이건 정말 편리한 점이 아닐 수 없었다.
또한 이 소스를 열기 위해선 소스를 해석해 줄 Next.js환경을 세팅해야 했다. Next.js는 사용자 인터페이스를 만들기 위한 자바스크립트 라이브러리인 React를 기반으로 하는 풀스택 웹 프레임워크라고 한다. 개발자에게 굉장히 유용하고 편리한 환경을 제공하는 도구로 실제 나이키, 클로드, 넷플릭스, 틱톡, 우버 등 다양한 유명 기업들도 이 Next.js를 사용 중이라고 한다.
그 외 Next.js 공식 홈페이지에서 어떤 회사들이 Next.js를 활용하고 있는지 알 수 있다.
사실 필자는 개발, 특히 웹 관련은 워낙 오래전에 그만두었기에 이런 디테일한 부분은 잘 이해할 수 없었고, 또 중요하지도 않았다. 이 지루하고 현학적인 이론과 개발에 관한 부분은 AI가 맡아서 할 일이지 필자가 할 일이 아니기 때문이다. 필자가 할 일은 AI가 이해하기 좋은 양질의 명령(Prompt)과 전반적인 UI/UX 기획, AI가 잡아내지 못 한 소소한 디테일을 신경 써 잡아내는 것이다.
Next.js는 컴퓨터에 Node.js가 설치되어 있어야 사용이 가능했다. Node.js 공식 홈페이지에서 받을 수 있다. 다음은 제미나이가 시키는 대로 차근차근 따라했다.
우선 권한 문제가 발생하지 않는 안전한 폴더(바탕화면)로 이동한다.
bashcd %userprofile%\Desktop
바탕화면에 my-archive 라는 폴더를 만들고 모든 질문을 Yes로 답하도록 명령어를 입력한다.
bashnpx -y create-next-app@latest my-archive
설치가 끝나면 그 폴더로 이동한 뒤, 앞서 코드에서 사용한 아이콘과 애니메이션 라이브러리를 설치한다.
bashcd my-archive npm install lucide-react framer-motion
여기까지 따라왔으면 my-archive -> app -> blog 폴더를 만들고, 그 안에 page.tsx 파일을 만든 다음 위에 제미나이가 작성한 코드를 복사한다.
마지막으로 서버를 실행한다.
bashnpm run dev
이제 브라우저에 http://localhost:3000/blog 를 입력하여 정상적으로 페이지가 뜨는지 확인한다. 여기서 포트 번호가 3000번인 이유는, Next.js 가 사용하는 기본 포트가 3000이기 때문이라고 한다. 참고로, 다른 도구인 Vite는 5173 , Vue는 8080 등을 기본값으로 사용한다고 한다. (서로 최대한 겹치지 않고 잘 사용될 일 없는 포트들을 기본 포트로 지정해 두었다고 생각하면 편하다.)
테스트가 끝나서 서버를 종료하고 싶다면 로컬 서버를 연 도스창에 Ctrl + C 를 입력하면 된다.
4. 상세 페이지 만들기 메인 페이지를 작성하며 기틀을 잡았기 때문에 상세 페이지(본문)를 작성하는 프롬프트는 크게 어렵지 않았다.
prompt이 메인페이지는 충분히 마음에 들어. 이제 여기서 각 항목(MOCK_POSTS) 을 클릭하면 그 상세페이지로 이동하게끔 하고 싶은데, 지금 이 무드와 UI/UX를 유지한 채로 상세 페이지 코드도 작성해줘.
우선, 동적 라우팅 폴더가 필요하다 해서 먼저 만들어 주었다. Next.js는 주소창에 /blog/1, /blog/2 처럼 글 번호나 영문 주소에 따라 페이지가 바뀌게 하려면 대괄호를 사용해 폴더를 만들어야 한다.
따라서 app -> blog 폴더 안에 [id] 라는 이름의 폴더를 새로 만들고, 그 안에 page.tsx 파일을 생성한 후, 아래 코드를 붙여넣는다.
TypeScript'use client'; import React from 'react'; import { motion, useScroll, useSpring } from 'framer-motion'; import Link from 'next/link'; import { ArrowLeft, Calendar, Clock, Hash, ChevronRight, Terminal } from 'lucide-react'; // 테스트용 목업 데이터 (이전 페이지와 동일한 데이터 중 1번 글의 상세 내용) const MOCK_POST = { id: 1, title: "Next.js App Router 환경에서의 다크모드 그라데이션 최적화 기법", category: "Engineering", tags: ["Next.js", "TailwindCSS", "Performance"], date: "2026-05-20", readingTime: "5 min read", author: "System Admin" }; export default function BlogPostPage({ params }: { params: { id: string } }) { // 상단 스크롤 진행도 표시 바 (Reading Progress) const { scrollYProgress } = useScroll(); const scaleX = useSpring(scrollYProgress, { stiffness: 100, damping: 30, restDelta: 0.001 }); return ( <div className="min-h-screen bg-[#0B0D10] text-[#EDEDED] font-sans antialiased selection:bg-[#38BDF8]/30 relative"> {/* 1. Reading Progress Bar */} <motion.div className="fixed top-0 left-0 right-0 h-1 bg-gradient-to-r from-[#38BDF8] to-[#A855F7] origin-left z-50" style={{ scaleX }} /> {/* 2. Top Navigation (Breadcrumb) */} <nav className="sticky top-0 z-40 bg-[#0B0D10]/80 backdrop-blur-md border-b border-[#1A1D23]"> <div className="max-w-6xl mx-auto px-6 h-14 flex items-center justify-between"> <Link href="/blog" className="flex items-center gap-2 text-sm text-[#A0A0A0] hover:text-[#EDEDED] transition-colors group"> <ArrowLeft size={16} className="group-hover:-translate-x-1 transition-transform" /> <span>Back to Archives</span> </Link> <div className="flex items-center gap-2 text-xs font-mono text-[#64748B]"> <span>Archive</span> <ChevronRight size={12} /> <span className="text-[#38BDF8]">{MOCK_POST.category}</span> </div> </div> </nav> <main className="max-w-6xl mx-auto px-6 py-12 lg:py-20 flex flex-col lg:flex-row gap-12 lg:gap-24 relative"> {/* 3. Main Content Area */} <article className="flex-1 w-full max-w-prose mx-auto lg:mx-0"> {/* Post Header */} <motion.header initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5 }} className="space-y-6 mb-16" > <div className="flex flex-wrap items-center gap-4 text-sm font-mono text-[#A0A0A0]"> <span className="text-[#38BDF8] bg-[#38BDF8]/10 px-2.5 py-1 rounded-md font-sans font-medium"> {MOCK_POST.category} </span> <span className="flex items-center gap-1.5"><Calendar size={14}/>{MOCK_POST.date}</span> <span className="flex items-center gap-1.5"><Clock size={14}/>{MOCK_POST.readingTime}</span> </div> <h1 className="text-3xl sm:text-4xl md:text-5xl font-extrabold tracking-tight text-[#EDEDED] leading-[1.2]"> {MOCK_POST.title} </h1> <div className="flex items-center gap-4 pt-6 border-t border-[#1A1D23]"> <div className="h-10 w-10 rounded-full bg-gradient-to-tr from-[#38BDF8] to-[#A855F7] p-[1px]"> <div className="h-full w-full bg-[#111318] rounded-full flex items-center justify-center"> <Terminal size={16} className="text-[#EDEDED]" /> </div> </div> <div className="text-sm"> <p className="font-medium text-[#EDEDED]">{MOCK_POST.author}</p> <p className="text-[#64748B]">Digital Creator & Engineer</p> </div> </div> </motion.header> {/* Post Body (Simulated Markdown Rendering) */} <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ duration: 0.5, delay: 0.2 }} className="prose prose-invert max-w-none prose-p:text-[#A0A0A0] prose-p:leading-relaxed prose-headings:text-[#EDEDED] prose-headings:font-bold prose-a:text-[#38BDF8] hover:prose-a:text-[#A855F7] prose-strong:text-[#EDEDED] prose-code:text-[#38BDF8] prose-code:bg-[#38BDF8]/10 prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded-md prose-code:before:content-none prose-code:after:content-none" > <p> 웹사이트를 설계할 때 다크모드는 이제 선택이 아닌 필수가 되었습니다. 특히 개발자나 디자이너를 타겟으로 하는 블로그나 포트폴리오 사이트에서는 눈의 피로를 덜어주는 어두운 배경이 훨씬 선호됩니다. </p> <h2 className="text-2xl mt-12 mb-6 border-b border-[#1A1D23] pb-4">1. 그라데이션 성능 이슈의 원인</h2> <p> 하지만 배경에 무거운 <code>filter: blur()</code> 효과나 복잡한 애니메이션 그라데이션을 넣으면, 특히 홈 서버나 저사양 디바이스에서 렌더링 프레임 드랍이 발생할 수 있습니다. </p> <blockquote className="border-l-4 border-[#38BDF8] pl-6 my-8 italic text-[#A0A0A0] bg-[#111318]/50 py-4 rounded-r-lg"> "UI의 화려함이 콘텐츠의 가독성을 해치거나 브라우저의 성능을 저하시켜서는 안 됩니다. 중요한 것은 균형입니다." </blockquote> <h2 className="text-2xl mt-12 mb-6 border-b border-[#1A1D23] pb-4">2. TailwindCSS를 활용한 해결책</h2> <p> Next.js와 TailwindCSS를 사용한다면 <code>::before</code>나 <code>::after</code> 가상 요소를 활용하여 하드웨어 가속을 받는 방식으로 그림자와 Glow 효과를 분리하는 것이 좋습니다. </p> {/* Code Block Mockup */} <div className="my-8 rounded-xl overflow-hidden border border-[#26292F] bg-[#0B0D10]"> <div className="flex items-center px-4 py-2 bg-[#111318] border-b border-[#26292F] text-xs font-mono text-[#64748B]"> <span>tailwind.config.js</span> </div> <pre className="p-4 overflow-x-auto text-sm font-mono text-[#A0A0A0]"> <code> <span className="text-[#A855F7]">module</span>.<span className="text-[#38BDF8]">exports</span> = {'{\n'} <span className="text-[#EDEDED]">theme</span>: {'{\n'} <span className="text-[#EDEDED]">extend</span>: {'{\n'} <span className="text-[#EDEDED]">boxShadow</span>: {'{\n'} <span className="text-[#38BDF8]">'glow'</span>: <span className="text-[#A0A0A0]">'0 0 20px rgba(56, 189, 248, 0.15)'</span>,\n {'}\n'} {'}\n'} {'}\n'} {'}'} </code> </pre> </div> <p> 이 방식을 사용하면 메인 스레드를 차단하지 않고도 GPU를 활용하여 매우 부드러운 전환 효과를 만들어낼 수 있습니다. </p> </motion.div> {/* Tags Section */} <div className="mt-16 pt-8 border-t border-[#1A1D23] flex flex-wrap gap-2"> {MOCK_POST.tags.map(tag => ( <span key={tag} className="inline-flex items-center gap-1 text-sm font-mono text-[#64748B] bg-[#111318] border border-[#26292F] px-3 py-1.5 rounded-lg hover:border-[#38BDF8]/50 hover:text-[#EDEDED] transition-colors cursor-pointer"> <Hash size={14} />{tag} </span> ))} </div> </article> {/* 4. Right Sidebar (Table of Contents - Desktop Only) */} <aside className="hidden lg:block w-64 shrink-0"> <div className="sticky top-24 space-y-6"> <h4 className="text-xs font-mono font-semibold text-[#EDEDED] uppercase tracking-wider flex items-center gap-2"> <Terminal size={14} className="text-[#38BDF8]" /> Table of Contents </h4> <nav className="flex flex-col gap-3 text-sm text-[#A0A0A0]"> <a href="#" className="hover:text-[#38BDF8] transition-colors border-l-2 border-[#38BDF8] pl-3 text-[#EDEDED]">1. 그라데이션 성능 이슈의 원인</a> <a href="#" className="hover:text-[#38BDF8] transition-colors border-l-2 border-transparent pl-3">2. TailwindCSS를 활용한 해결책</a> <a href="#" className="hover:text-[#38BDF8] transition-colors border-l-2 border-transparent pl-3">3. 실제 적용 사례</a> <a href="#" className="hover:text-[#38BDF8] transition-colors border-l-2 border-transparent pl-3">4. 결론 및 요약</a> </nav> <div className="pt-6 mt-6 border-t border-[#1A1D23]"> <button className="w-full py-2 px-4 rounded-lg border border-[#26292F] bg-[#111318] text-[#EDEDED] text-xs font-mono hover:bg-[#26292F] transition-colors flex items-center justify-center gap-2"> <ArrowLeft size={14} /> Return to Top </button> </div> </div> </aside> </main> </div> ); }
그 다음, 위에서 작성한 page.tsx 파일도 새로 작성해주어야 했다.
TypeScript'use client'; import React, { useState } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { Search, Terminal, Calendar, Clock, ArrowUpRight, Hash } from 'lucide-react'; import Link from 'next/link'; // 👈 [핵심 수정 1] Link 컴포넌트를 불러오는 코드 추가! // 1. Mock Data (실제 마크다운/MDX 연동 전 테스트 데이터) const CATEGORIES = ['All', 'Engineering', 'Design', 'Narrative', 'Research']; const MOCK_POSTS = [ { id: 1, title: "Next.js App Router 환경에서의 다크모드 그라데이션 최적화 기법", description: "홈 서버의 대역폭 한계를 극복하고 CSS 렌더링 블로킹을 최소화하기 위한 은은한 Glow 레이어 및 Glassmorphism 최적화 가이드입니다.", category: "Engineering", tags: ["Next.js", "TailwindCSS", "Performance"], date: "2026-05-20", readingTime: "5 min read", isFeatured: true, }, { id: 2, title: "Linear와 Vercel의 디자인 시스템 분석: 미니멀리즘과 엔지니어링의 결합", description: "과도한 네온 컬러를 배제하고 가독성을 극대화하는 다크 테마 컬러 팔레트 구축 및 타이포그래피 정렬 방식을 심층 분석합니다.", category: "Design", tags: ["DesignSystem", "UIUX", "DarkTheme"], date: "2026-05-18", readingTime: "8 min read", isFeatured: false, }, { id: 3, title: "스페이스 오페라 장르에서의 양자 도약 세계관 및 인물 관계도 설계 노하우", description: "위키 형태의 아카이브 구축을 위한 하이퍼링크 기반 설정 문서화 방법과 대서사시의 시간선 흐름을 제어하는 프레임워크.", category: "Narrative", tags: ["WorldBuilding", "GameDev", "Writing"], date: "2026-05-15", readingTime: "12 min read", isFeatured: false, }, { id: 4, title: "i5-8400 기반 홈 서버에서 Docker 컨테이너 리소스 격리 및 모니터링", description: "RAID 1 볼륨의 디스크 I/O 병목을 방지하고 Nginx 리버스 프록시 캐싱 전략을 통한 응답 속도 향상 실험 기록.", category: "Research", tags: ["NAS", "Docker", "Nginx"], date: "2026-05-10", readingTime: "6 min read", isFeatured: false, }, ]; export default function BlogPage() { const [activeCategory, setActiveCategory] = useState('All'); const [searchQuery, setSearchQuery] = useState(''); // 필터링 로직 const filteredPosts = MOCK_POSTS.filter(post => { const matchesCategory = activeCategory === 'All' || post.category === activeCategory; const matchesSearch = post.title.toLowerCase().includes(searchQuery.toLowerCase()) || post.description.toLowerCase().includes(searchQuery.toLowerCase()) || post.tags.some(tag => tag.toLowerCase().includes(searchQuery.toLowerCase())); return matchesCategory && matchesSearch; }); return ( <div className="min-h-screen bg-[#0B0D10] text-[#EDEDED] font-sans antialiased selection:bg-[#38BDF8]/30"> {/* Background Subtle Glow Line */} <div className="absolute top-0 left-0 right-0 h-[1px] bg-gradient-to-r from-transparent via-[#26292F] to-transparent" /> <main className="max-w-6xl mx-auto px-6 py-16 space-y-12 relative z-10"> {/* Header Section */} <div className="flex flex-col md:flex-row md:items-end justify-between border-b border-[#1A1D23] pb-8 gap-6"> <div className="space-y-2"> <div className="flex items-center gap-2 text-xs font-mono text-[#38BDF8]"> <Terminal size={14} /> <span>SYSTEM_ACTIVE.LOG</span> </div> <h1 className="text-3xl font-bold tracking-tight text-[#EDEDED]">리미트로그의 아카이브</h1> <p className="text-[#A0A0A0] max-w-md text-sm leading-relaxed"> 디지털 실험 기록, 개발 로그, 디자인 시스템 및 내러티브 기획을 보존하는 독립 연구실 공간입니다. </p> </div> {/* Lab Metadata Display */} <div className="flex gap-6 text-xs font-mono text-[#A0A0A0] bg-[#111318] border border-[#26292F] px-4 py-3 rounded-xl"> <div> <span className="text-[#64748B]">TOTAL_LOGS:</span> <span className="text-[#EDEDED]">{MOCK_POSTS.length}</span> </div> <div className="hidden sm:block border-l border-[#26292F] h-4" /> <div className="hidden sm:block"> <span className="text-[#64748B]">LAST_INDEXED:</span> <span className="text-[#EDEDED]">2026.05.20</span> </div> </div> </div> {/* Search & Filter Bar (Linear / Raycast Style) */} <div className="flex flex-col sm:flex-row gap-4 items-center justify-between"> {/* Category Tabs */} <div className="flex items-center gap-1.5 p-1 bg-[#111318] border border-[#26292F] rounded-xl overflow-x-auto w-full sm:w-auto"> {CATEGORIES.map((category) => ( <button key={category} onClick={() => setActiveCategory(category)} className={`px-3 py-1.5 text-xs font-medium rounded-lg transition-all duration-200 whitespace-nowrap ${ activeCategory === category ? 'bg-[#26292F] text-[#38BDF8] shadow-sm' : 'text-[#A0A0A0] hover:text-[#EDEDED]' }`} > {category} </button> ))} </div> {/* Search Input */} <div className="relative w-full sm:w-72 group"> <Search className="absolute left-3.5 top-1/2 -translate-y-1/2 text-[#64748B] group-focus-within:text-[#38BDF8] transition-colors duration-200" size={16} /> <input type="text" placeholder="Search archives..." value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} className="w-full bg-[#111318] border border-[#26292F] focus:border-[#38BDF8]/50 focus:outline-none rounded-xl pl-10 pr-4 py-2 text-xs text-[#EDEDED] placeholder-[#64748B] transition-all duration-300 focus:shadow-[0_0_20px_rgba(56,189,248,0.05)]" /> </div> </div> {/* Posts Grid Container */} <div className="space-y-8"> <AnimatePresence mode="popLayout"> {filteredPosts.map((post, index) => { // Featured Post Layout (Bento 스타일로 최상단에 넓게 배치) if (post.isFeatured && activeCategory === 'All' && searchQuery === '') { return ( <Link href={`/blog/${post.id}`} key={post.id} className="block group col-span-2"> <motion.article layout initial={{ opacity: 0, y: 15 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, scale: 0.98 }} transition={{ duration: 0.4 }} className="relative bg-[#111318] border border-[#26292F] hover:border-[#38BDF8]/30 rounded-2xl p-6 md:p-8 flex flex-col justify-between gap-6 transition-all duration-300 cursor-pointer overflow-hidden shadow-2xl" > {/* Background Subtle Gradient Shadow */} <div className="absolute -right-20 -top-20 w-64 h-64 bg-gradient-to-br from-[#38BDF8]/5 to-[#A855F7]/0 rounded-full blur-3xl opacity-0 group-hover:opacity-100 transition-opacity duration-500" /> <div className="space-y-4 relative z-10"> <div className="flex items-center gap-3 text-xs font-mono text-[#A0A0A0]"> <span className="text-[#38BDF8] bg-[#38BDF8]/10 px-2 py-0.5 rounded-md font-sans font-medium">{post.category}</span> <span className="flex items-center gap-1"><Calendar size={12}/>{post.date}</span> <span className="flex items-center gap-1"><Clock size={12}/>{post.readingTime}</span> </div> <h2 className="text-xl md:text-2xl font-bold tracking-tight text-[#EDEDED] group-hover:text-[#38BDF8] transition-colors duration-200 flex items-start justify-between gap-4"> <span className="leading-snug">{post.title}</span> <ArrowUpRight className="text-[#64748B] group-hover:text-[#38BDF8] group-hover:translate-x-0.5 group-hover:-translate-y-0.5 transition-all duration-200 shrink-0 mt-1" size={20} /> </h2> <p className="text-sm text-[#A0A0A0] leading-relaxed max-w-3xl"> {post.description} </p> </div> <div className="flex flex-wrap gap-1.5 pt-4 border-t border-[#1A1D23] relative z-10"> {post.tags.map(tag => ( <span key={tag} className="inline-flex items-center gap-0.5 text-xs font-mono text-[#64748B] bg-[#0B0D10] border border-[#1A1D23] px-2 py-1 rounded-lg"> <Hash size={10} />{tag} </span> ))} </div> </motion.article> </Link> ); } // 👈 [핵심 수정 2] 일반 리스트 카드 형태도 Link로 감쌌습니다! return ( <Link href={`/blog/${post.id}`} key={post.id} className="block group"> <motion.article layout initial={{ opacity: 0, y: 15 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, scale: 0.98 }} transition={{ duration: 0.3, delay: index * 0.05 }} className="bg-[#111318]/60 border border-[#26292F] hover:border-[#A855F7]/30 rounded-xl p-5 flex flex-col justify-between gap-4 transition-all duration-300 cursor-pointer relative overflow-hidden" > <div className="space-y-2.5"> <div className="flex items-center gap-3 text-xs font-mono text-[#A0A0A0]"> <span className="text-[#A855F7] bg-[#A855F7]/10 px-2 py-0.5 rounded-md font-sans font-medium">{post.category}</span> <span className="flex items-center gap-1"><Calendar size={12}/>{post.date}</span> <span className="flex items-center gap-1"><Clock size={12}/>{post.readingTime}</span> </div> <h3 className="text-base font-semibold tracking-tight text-[#EDEDED] group-hover:text-[#A855F7] transition-colors duration-200 flex items-center justify-between gap-2"> <span>{post.title}</span> <ArrowUpRight className="text-[#475569] group-hover:text-[#A855F7] transition-colors duration-200 shrink-0" size={16} /> </h3> <p className="text-xs text-[#A0A0A0] leading-relaxed line-clamp-2"> {post.description} </p> </div> <div className="flex flex-wrap gap-1 pt-2 border-t border-[#1A1D23]/50"> {post.tags.map(tag => ( <span key={tag} className="inline-flex items-center gap-0.5 text-[10px] font-mono text-[#64748B] px-1.5 py-0.5"> #{tag} </span> ))} </div> </motion.article> </Link> ); })} </AnimatePresence> {/* Empty State */} {filteredPosts.length === 0 && ( <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} className="text-center py-20 border border-dashed border-[#26292F] rounded-2xl bg-[#111318]/20" > <Terminal className="mx-auto text-[#64748B] mb-3" size={24} /> <p className="text-sm font-mono text-[#64748B]">NO_ARCHIVES_FOUND_MATCHING_THE_CRITERIA</p> </motion.div> )} </div> </main> </div> ); }
여기까지 하면 기본적인 UI가 잡힌 블로그가 나온다. 사실 첫 프롬프트를 입력하는 과정을 제외하면 추가로 발생하는 오류를 수정하거나, 상세 페이지를 만드는 프롬프트는 누구나 할 수 있을 정도로 명령을 내리는 방식이 쉽다. 여기서 필자가 블로그에 관해서 추가로 건드린 부분은 관리의 편의성을 위해 글을 모아두는 페이지 파일을 만들거나(현재는 어떤 게시글 썸네일을 클릭해도 [id]/page.tsx 에 직접 입력된 하나만 표시된다.) 각 게시글 파일을 본문 내용만 따로 나누어 관리하게끔 하고, 본문 작성 방식을 마크다운 (일반 텍스트로 서식이 있는 문서를 작성할 수 있는 경량 마크업 언어.) 으로 바꾸는 작업 정도이다. 그 외 디자인적 디테일? 하지만 작업이 전혀 어렵지 않았다.
예를 들어, 본문 작성 규칙을 마크다운으로 바꿀 때는 다음과 같이 입력했다.
prompt글을 작성할 때 이미지 들어오거나 할 때 하나하나 중괄호 열고 type 입력하고 글 치는 거 상당히 불편한데 더 효율적인 방법이 있을까? 예를 들면 전부 마크다운 형식으로 바꿔버린다던지.
아, 참고로 필자는 미크다운 문법을 아예 몰랐기에 다음과 같이 블로그를 작성하고 있었다.
▲ 다시 봐도 끔찍하다.
그러자 마크다운 텍스트를 React 컴포넌트로 변환해 주는 가장 유명한 라이브러리 설치 방법부터 코드 수정까지 전부 세세하게 알려주었다. 뿐만 아니라, 기존에 작성한 본문 소스를 마크다운 형식으로 변환까지 일사천리로 진행해 주었다. 내가 추가하고 싶은 규칙이 있으면, 그대로 전달만 하면 된다.
이젠 정말 누구나 개발에 입문할 수 있는 세상이 된 것 같다. 개발 뿐이 아니다. 무언가에 관심을 갖고 입문하는 난이도가 과거에 비해 월등히 낮아졌다. AI가 낯설 수는 있으나 인간의 삶을 윤택하게 하기 위해 나온 도구인 만큼, 사용하다 보면 크게 어렵지 않다. 물론 필자의 AI 활용 능력도 높은 편은 아니다. 하지만 나름대로 실생활에 적용하여 이것저것 시도해 보는 중이다. 요즘 보면 AI를 다루기 위해 배우러 다니는 경우가 종종 보인다. 물론 효율적으로 프롬프트를 작성하는 방법이 있기야 할 것이다. 하지만 그런 내용은 따로 비용을 들이지 않고 인터넷에서 찾아봐도 어느 정도 알 수 있다. 오히려 필자는 그 시간에 내가 다룰 분야의 기본적인 내용을 공부해 본인의 배경지식을 바탕으로 AI에게 더 효율적인 명령을 내리는 것이 좋다고 생각한다.
말이 조금 샌 것 같은데, 이 포스트에 호스팅 과정까지 다 담기에는 너무 길어질 것 같아 다음 포스트에서 필자를 힘들게 했던 Truenas를 활용한 호스팅 과정을 작성해 보도록 하겠다.