next

next.js, gemini AI를 이용한 한국인 캐릭터 생성기 - 3

weaklion 2025. 1. 23. 18:10

 

저번까지는 Intro였고요. 오늘부터는 본격적으로 코딩을 해봅시다. 다만 코드의 구조가 간단하고, 얼마 없는지라 꽤나 싱겁게 끝날 수도 있어요.

 

이전에 말했다시피 저는 app router를 사용했습니다. app router를 쓰게 되면 기본적으로 

위와 같이 폴더가 만들어질겁니다.

 

처음 프로젝트가 만들어지면 기본으로 next.js 템플릿 스타일과 페이지가 있을텐데, 지웁시다. 그리고 메인화면을 원하는대로 수정합시다.

 

여기서부터 코드를 어떻게 짜야하는지 하나하나 가르쳐 드리진 않겠습니다. 저도 아직 next.js를 모르고, react도 애기 수준이라 코드 작성법은 오히려 다른 분의 블로그가 더 잘 되있을거에요.

그래서 지금부턴 제가 짠 코드를 보여드리고, 왜 이런식으로 짰는지에 대해서만 설명을 드리도록 하겠습니다.

우선 폴더 구조 입니다.

 

app 폴더는 기본적으로 app router를 했을 때 생성되있는 폴더입니다. typescript로 구성했기 떄문에 필요한 타입 정의를 위해 model이라는 폴더를 만들었고, 그 안에서 타입을 정의 했습니다.

components라는 폴더엔 공용으로 사용할 ui들이 정의 되있는데요. 여기서 폴더 구조를 보면 슬슬 눈치채셨겠지만, components 폴더는 제가 스스로 만든 게 아니라 shadcn 라이브러리를 사용했기 때문에 생성된 폴더입니다.

https://ui.shadcn.com/docs

 

현재 리액트에서 가장 인기가 많은 css 라이브러리로, shadcn을 이용해 컴포넌트를 구축하기 위해선, components 폴더와 ui폴더가 있어야만 합니다. shadcn 내부에서 tailwindcss를 사용하기 때문에 그것도 포함이 되어야 하고요.

 

shadcn 에서 가져온 컴포넌트들은 모두 ui에 만들어집니다. 그외에 제가 사용할 컴포넌트들은 그냥 ui 폴더 바깥에다가 만들었습니다.

"use client";

import { ChevronLeft } from "lucide-react";
import { Button } from "./ui/button";
import { useRouter } from "next/navigation";

export default function Header() {
  const router = useRouter();

  const onClickBackButton = () => {
    router.back();
  };

  return (
    <header className="sticky top-0 z-30 flex h-14 items-center bg-background">
      <Button
        size="icon"
        variant="ghost"
        className="ml-4"
        onClick={onClickBackButton}
      >
        <ChevronLeft />
      </Button>
    </header>
  );
}
// components/header.tsx

 

간단하게 만들어 본 header입니다. 이전에도 말했다시피 app router는 server와 client 컴포넌트를 따로 구분하기 때문에, header 컴포넌트는 클라이언트에서 사용하기 위해 'use client'를 사용했습니다.

 

export default function Loader() {
  return (
    <div role="status" className="flex justify-center">
      <svg
        aria-hidden="true"
        className="w-8 h-8 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600"
        viewBox="0 0 100 101"
        fill="none"
        xmlns="http://www.w3.org/2000/svg"
      >
        <path
          d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
          fill="currentColor"
        />
        <path
          d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
          fill="currentFill"
        />
      </svg>
      <span className="sr-only">Loading...</span>
    </div>
  );
}
// components/loader.tsx

 

로더는 로더..svg로 그냥 로더만 돌아가도록 구성했습니다. 딱히 뭐 없어요.

 

lib 폴더도 shadcn을 설치하면 추가되는 폴더입니다. 컴포넌트 내부에서 tailwind-merge를 쓰기 위함으로, 알 필요는 없지만 굳이 알자면 이미 컴포넌트 내부에서 구현한 인라인 클래스 스타일과 밖에서 선언한 클래스 스타일이 중복되는 게 있을 때 override하여 밖에서 선언한 클래스로 선언하게 만들어주는 라이브러리 입니다. 

(ex : 컴포넌트 내부에서 container 스타일을 px-1 으로 선언하고, 외부에서 p-3로 선언했을 때 p-3로 만들어줌)

 

tailwindcss만을 위한 라이브러리로, shadcn 내부 컴포넌트에서 주로 사용됩니다.

 

model 폴더엔 컴포넌트나 api 통신시 사용되는 타입들을 선언했습니다. 지금은 작은 프로젝트라 index.ts 하나만으로 가능하겠지만, 규모가 커진다면 더 깔끔하게 바꿔야겠죠.

export interface Character {
  name: string;
  age: number;
  gender: string;
  appearance: string;
  background: string;
  occupation: string;
}

export interface CharacterBackground {
  name: string;
  detailedBackground: {
    familyRelations: string;
    growthEnvironment: string;
    educationalBackground: string;
    lifeExperiences: string;
    pathToCurrentSituation: string;
    personalityShapingEvents: string;
    keyRelationships: string;
    dreamsAndGoals: string;
    strengthsAndWeaknesses: string;
    specialSkillsOrTalents: string;
  };
}

model/index.ts

 

Charcter 인터페이스는 추후 api를 호출 할 때 필요한 타입으로, 추후  gemini 와 데이터를 주고받을 때 사용될 타입입니다. 

CharacterBackground는 캐릭터의 자세한 배경을 알고싶을 때 사용하는 타입으로, 추후 둘을 어떻게 사용할지 코드를 보시면 더 잘 이해가 될 것 같네요.

 

이제 마지막으로 app 폴더만 남았네요. 여기서부터가 진짜인데, 진짜는 다음 시간에 알아보겠습니다.