오늘은 app 폴더 내부에 대해 알아보도록 합시다.
여담인데 최근 rust 언어를 공부하고 있습니다.
개발을 점점 하다보니 로우 랭귀지를 배울 필요성을 느껴서 공부를 시작했는데 배워보니까 꽤나 어렵고 흥미로운 언어더군요.
시간이 난다면 rust에 대해서도 포스트를 남겨보도록 하겠습니다.
다시 돌아와서. 우선 저번에도 말했다 시피 간단한 프로젝트라 페이지를 많이 만들지 않았습니다.
main으로 쓸 main 페이지 폴더, 그리고 결과가 나올 list. 더 구체적인 결과를 나타낼 detail 페이지. 총 3가지입니다.
next는 client와 server를 나눌 것을 권장하고 있는데, 저는 client에서 사용될 코드를 client.tsx로 정의했고, route의 시작지점이 될
페이지이자 서버 컴포넌트는 page.tsx로 정의했습니다.

처음 보여질 페이지는 main입니다. 소괄호 안에 이름을 적고 폴더를 만든다면 그 route로 이동하지 않고 코드를 작성할 수 있습니다.
ex) 만약 main 폴더를 만들었다면 /main 의 라우트가 생기겠지만, 소괄호로 감싼다면 생기지 않습니다.
가장 상위에 만들었기 때문에 localhost:3000. 즉 첫 페이지엔 (main)폴더 안에 page.tsx가 실행이 되겠죠.
import { Suspense } from "react";
import Client from "./client";
import Loader from "@/components/loader";
export default function Main() {
return (
<div>
<Suspense fallback={<Loader />}>
<Client />
</Suspense>
</div>
);
}
//(main)/page.tsx
<suspense>는 컴포넌트의 자식 요소가 렌더링 되기 전 로딩을 선언함으로써 사용자의 경험을 개선할 수 있게 합니다.
위 코드를 해석하자면 client 페이지가 렌더링 되기 전 loader 컴포넌트를 띄우고 렌더링을 마치면 client가 띄어지는 거죠.
"use client";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Button } from "@/components/ui/button";
import { useState } from "react";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { Input } from "@/components/ui/input";
import Loader from "@/components/loader";
export default function Home() {
const [gender, setGender] = useState<"man" | "women">("man");
const [count, setCount] = useState(1);
const [loading, setLoading] = useState(false);
const router = useRouter();
const searchParams = useSearchParams();
const pathname = usePathname();
const setQueryParams = (params: {
gender: "man" | "women";
count: string;
}) => {
const urlSearchParams = new URLSearchParams(searchParams);
Object.entries(params).forEach(([name, value]) => {
urlSearchParams.set(name, value);
});
return urlSearchParams.toString();
};
const handleGenderChange = (value: "man" | "women") => {
setGender(value);
};
const onCreateCharacter = () => {
const params = {
gender: gender,
count: count.toString(),
};
const qs = setQueryParams(params);
setLoading(true);
router.push(`/list?${qs}`);
};
return (
<main className="flex min-h-screen flex-col items-center justify-center p-12">
<h1 className="scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl">
한국인 캐릭터 생성기
</h1>
<h3 className="scroll-m-20 my-4 text-2xl font-semibold tracking-tight">
성별
</h3>
<Select
onValueChange={(value: "man" | "women") => handleGenderChange(value)}
>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="성별을 선택해주세요." />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="man">남자</SelectItem>
<SelectItem value="women">여자</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
<h3 className="scroll-m-20 my-4 text-2xl font-semibold tracking-tight">
개수
</h3>
<Input
type="number"
className="max-w-48"
value={count}
onChange={(e) => setCount(e.target.valueAsNumber)}
placeholder="개수를 입력해주세요."
/>
<Button className="my-4" onClick={onCreateCharacter}>
생성
</Button>
{loading ? (
<div className="flex flex-col py-4 gap-2 justify-center">
<Loader />
잠시만 기다려 주세요..
</div>
) : (
""
)}
</main>
);
}
//(main)/client.tsx
client.tsx 코드입니다. 지금은 코드를 다 보여줬지만, 이 이후론 제가 코딩하면서 인상깊었던 코드에 대해서만 적을까 합니다.
return 되는 템플릿엔 특별한 부분이 없으니 넘어가고, 다른 것 중 특이한 건 라우팅을 하는 방식이었습니다.
저는 이전번에 vue를 쓰고 vue-router를 쓰면서 routing에 썩 불편함을 느끼진 못했습니다. 하지만 이번에 next의 라우팅을 쓰면서 처음으로 불편함을 느꼈습니다.
첫번째로 버전이 올라가면서 쿼리스트링을 통한 방식이 바뀌었다는 점.
useRouter를 이용해서 하는 이전 방식과 다르게 이번엔 url에 원하는 queryString을 직접 집어 넣어야만 합니다. URLSearchParams 인터페이스를 이용해서 하나하나씩 set 해줘야만 하죠.
물론 저도 찾다가 답이 안나와서 한 거긴 합니다. 더 좋은 방식이 있으면 답변해주시면 감사하겠습니다.
저는 아레 레퍼런스를 참조했습니다.
https://developer.mozilla.org/ko/docs/Web/API/URLSearchParams
URLSearchParams - Web API | MDN
URLSearchParams 인터페이스는 URL의 쿼리 문자열을 대상으로 작업할 수 있는 유틸리티 메서드를 정의합니다.
developer.mozilla.org
https://medium.com/@lucarestagno/query-string-params-in-next-js-14-with-app-router-df44c8efa1f9
Query string params in Next.js (14) with App Router
The query string is a part of the URL (Uniform Resource Locator) that assigns values to specific parameters.
medium.com
urlSearchParams로 queryString을 집어넣고 그 url을 router로 push 합니다.
router.push({ name : "..." , params : {...} })로 딸깍하다가 이걸 다른 방식으로 하더니 영 답답했습니다.
두 번째론 그냥 next js가 불편하더군요. 버전이 올라갈수록 방식 자체가 바뀌는지라 정보를 찾기가 힘들었습니다. 이 부분은 라우팅 뿐만 아니라 다른 것도 마찬가지인지라 next.js를 쓰는 다른 유저들도 만만찮게 불편을 토로하더군요.
어쨌든 router.push로 변환한 url을 넣으면 라우팅을 하는데, 이 때 input에 넣은 값들을 ai의 프롬프트에 집어 넣습니다.
그 과정에서 원래는 suspense로 처리하면 될 걸, 굳이 loading을 넣어서 처리했는데요.
이건 api에 값을 requeest했을 때 곧바로 200으로 상태값이 떨어지는데도 response를 늦게 불러와서 제가 loading을 제어하기 위해 넣었습니다. 이건 프롬프트에 대한 결과를 다룰 때 더 자세히 봅시다.
이제 gemini와 통신하기 위한 api를 만들어야 합니다. 저는 next.js의 서버 액션을 통해 api를 만들었는데요. 그 전에 gemini와 연동하기 위한 준비를 해야겠죠.
그 과정은 다음 시간에... 알아 보도록 하겠습니다.
'next' 카테고리의 다른 글
next.js, gemini AI를 이용한 한국인 캐릭터 생성기 - 5 (0) | 2025.03.27 |
---|---|
next.js, gemini AI를 이용한 한국인 캐릭터 생성기 - 3 (0) | 2025.01.23 |
next.js, gemini AI를 이용한 한국인 캐릭터 생성기 - 2 (0) | 2025.01.09 |
next.js, gemini AI를 이용한 한국인 캐릭터 생성기 - 1 (1) | 2024.12.29 |
next.js, gemini AI를 이용한 한국인 캐릭터 생성기 - 0 (0) | 2024.09.11 |