항상 프로젝트를 새로 시작하면 폴더와 파일 구조는 매번 바뀌었다. 이게 맞다, 저게 맞다 이 구조에서는 이거다! 등 항상 고민을 했었다.
그러다 vscode 에서 파일구조 관련옵션 기능이 도입되었고, 주로 Nextjs 프로젝트를 진행하고있는 내 입장에서는 vscode 에서 사용가능한 파일구조 관련옵션 기능을 잘 사용하면 내가 원하는 폴더구조를 만들 수 있다는 생각과 확신이 들었다.
이 확신은 아래와 같은 상황에서 적용된다
async function Page() {
const getName = await getName();
return <div>hello {name} world</div>
}
function getName() {
const sql = `SELECT name FROM User WHERE id="jimmy";
const name = await db.excute(sql);
return name;
}
위 구조에서 일반적으로 Nextjs 프로젝트 기준 Page에 로딩상태가 들어가야한다면 loading.tsx 파일을 생성하는게 가장 간단 할 것이다
문제는 일반적으로 Page가 아래 구조같이 길어진다는 것이다
async function Page() {
const name = await getName();
return <>
+ <SomItem />
+ <SomItem />
+ <SomItem />
<div>hello {name} world</div>
</>
}
이때 우리는 static한 SomeItem 컴포넌트는 로딩상태가 필요 없고, 즉각적으로 사용자에게 보여야 하는데 getName 함수 하나 때문에 전체 Loading을 태울 필요가 있냐는 것이다
결과적으로 getName 을 담당하는 함수롤 하나 생성하고 해당 함수만 비동기로 동작시키기 위해 아래와 같이 바운더리를 생성해야 한다
+async function Name() {
+ const name = await getName();
+ return <div>hello {name} world</div>
+}
function Page() {
- const name = await getName();
return <>
<SomItem />
<SomItem />
<SomItem />
+ <Suspnse fallback={<div>loading</div>}>
<Name />
+ </Suspense>
</>
}
에러 바운더리 역시 같은 맥락이다. name 하나 때문에 전체 페이지가 에러에 점령 당하면 안되기때문에
로딩 상태와 에러 상태를 각각 바운더리로 괸리 해야한다
이제는 유저 상호작용이다
유저와 상호작용이 된다는 것은 결과적으로 해당 컴포넌트 레벨을 "use client" 지시어로 감싸야 한다는 것이고 db에서 데이터를 받아오기 위한 server 컴포넌트랑은 분리되어야 한다는 것이다
따라서 최종적으로 필요한 컴포넌트는 아래와 같다
우리가 1번, 2번 컴포넌트까지만 만든다면 Name을 사용하기위해서 NameServer컴포넌트를 사용하는 곳마다 바운더리를 생성 해야 할 것이다
// a 페이지
function Page() {
return <Suspnse fallback={<div>loading</div>}>
<Name />
</Suspense>
}
// b 페이지
function Page() {
return <Suspnse fallback={<div>loading</div>}>
<Name />
</Suspense>
}
이걸 방지하고자
// name/Name.tsx
function Name() {
return <ErrorBoundary fallback={<div>error</div>}>
<Suspnse fallback={<div>loading</div>}>
<NameServer />
</Suspense>
</ErrorBoundary>
}
// name/Nam.server.tsx
async function NameServer() {
const name = await getName();
return <NameClient name={name} />
}
// name/Name.client.tsx
"use client"
function NameClient(props: {name: string}) {
return <div>hello {props.name} world</div>
}
따라서 Name을 소비하는 컴포넌트들은 Name만 가져오면 바운더가 제공되는 환경을 가져오게 되는 것이며, 필요 하다면 서버와 클라이언트 중 필요한 컴포넌트 하나를 가져와도 상관없다.
여기서 vscode에서 제공되는 옵션을 함께 사용하면 굉장히 심플한 구조가 완성되고, 추가로 에러컴포넌트(Table.error.tsx), 로딩컴포넌트(Table.loading.tsx)같은 의미론적인 컴포넌트에 대한 경계가 만들어지기때문에 추후 해당 프로젝트를 인수인계 한다고 할때도 컴포넌트의 역할가 기능을 명확하게 인지 시킬 수 있다고 생각한다

이구조를 완성하고 난 뒤에 비슷한 맥락으로 사용되고있는 곳을 찾아 봤으나 잭 헤링턴 아저씨도 흡사한 구조를 사용하고있는 것 같아서 굉장히 .. 굉장히 마음이 놓인다.