프로젝트를 새로 시작하면 항상 폴더와 파일 구조는 매번 바뀌었다.
그러다 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 함수 하나 때문에 해당 Page에 대한 전체 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 컴포넌트랑은 분리되어야 한다는 것이다
따라서 최종적으로 필요한 컴포넌트의 파일구조는 아래와 같다
문제점 발생: 아래같이 Name 서버컴포넌트를 소비하는곳 마다 별도의 서스펜스를 작성해주어야한다. (에러바운더를 포함한)
// a 페이지
function Page() {
return <Suspnse fallback={<div>loading</div>}>
<Name />
</Suspense>
}
// b 페이지
function Page() {
return <Suspnse fallback={<div>loading</div>}>
<Name />
</Suspense>
}
이를 방지하고자 Nmae 컴포넌트의 구조를 아래와 같이 반경한다
// 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)같은 의미론적인 컴포넌트에 대한 경계가 만들어지기때문에 추후 해당 프로젝트를 인수인계 한다고 할때도 컴포넌트의 역할가 기능을 명확하게 인지 시킬 수 있다고 생각한다

이 구조는 앞으로 우리가 사용해야 할 지시어들에 의해서 강제될 것으로 생각된다
예를들면 Nextjs cache 에서도 나와있다시피
아무리 간단한 내용을 가진 컴포넌트라고 해도
async function getName() {
// access db or request http
return name;
}
async function Page() {
const name = awit getName();
return <div>{name}</div>
}
getName 이라는 비즈니스적 값어치와 use cache 지시어 때문에 컴포넌트는 분리되어야 하기때문 (Page, NameItem, getName)
async function NameItem() {
"use cache"
const name = awit getName();
return <div>{name}</div>
}
function Page() {
return <NameItem />
}
이때 분리되는 기준 해석을 아래와 같이 할 수 있다
소비처: Page 소비주체: Name 이라는 도메인 도메인: Name 도메인이 가지는 구조:
이구조를 완성하고 난 뒤에 비슷한 맥락으로 사용되고있는 곳을 찾아 봤으나 잭 헤링턴 이라는 분도 흡사한 구조를 사용하고있는 것 같아서. 나 혼자만의 개념은 아니구나 라는 생각은 든다.