사람함께에이전트▾
사람 — 직접 정하고 책임지는 비즈니스 규칙과 흐름. 직접 읽어보세요.
함께 — 개념은 알아두고, 세부 규칙은 에이전트가 따릅니다.
에이전트 — 에이전트가 따르는 규칙과 레퍼런스. 필요할 때 찾아보세요.
앱 & 라이브러리▾
도메인▾
스칼라▾
Service.Zone.tsx
Zone은 검색 콘솔, 업로드 패널, 장비 대시보드처럼 page가 통째로 끼워 넣는 화면 구획입니다. service module에는 대개 필요 없습니다. 이 워크스페이스의 service module 여덟 개 중 Zone을 가진 것은 하나도 없고, Util도 마찬가지입니다.
나열할 레코드가 없기 때문입니다. service module의 UI는 보통 구획이 아니라 자기만의 화면이거나, 다른 화면 속 버튼 하나입니다.
이 페이지에서 쓰는 말
용어설명
service module
자기 model 없이 service, signal, dictionary, 그리고 흔히 store로 이루어진
lib/_<name> 폴더입니다.page가 통째로 끼워 넣는 구획을 맡는 파일 역할입니다. 언제나 클라이언트 컴포넌트입니다.
"use client"가 없는 파일입니다. 서버에서 실행되어 HTML로 도착합니다.
첫 줄이 "use client"인 파일입니다. HTML로 한 번, 브라우저가 다시 실행하는 JS로 한 번 더 도착합니다.
slot
header 같은 ReactNode prop입니다. page가 서버에서 내용을 그려 넘겨줍니다.UI 종류별 자리
UI의 모습
page/
ui/
.Zone.tsx
Zone이 아닌 경우
자기만의 화면
✓
libs/shared/page/oauth의 OAuth 동의 화면처럼 route 하나입니다.다른 화면 속 버튼 하나
✓
service store가 움직이는
ui/ 컴포넌트입니다. 드물게 Service.Util.tsx가 됩니다.route 하나에만 나오는 구획
✓
그 구획이 곧 그 route입니다. page 안에 바로 씁니다.
Zone인 경우
여러 route에서 다시 쓰는 구획
✓
store state를 함께 쓰는 컨트롤 여러 개가 한 덩어리로 배치된 것입니다.
✓여기에 둡니다여기가 아닙니다
구획이 Zone이 되려면 아래 세 가지를 모두 만족해야 합니다:
- 컨트롤이 여러 개입니다. 모두 같은 store state를 읽습니다.
- 한 덩어리로 배치됩니다. 화면 여기저기 흩어진 조각이 아니라 한 구획을 이룹니다.
- route 두 곳 이상에서 다시 씁니다. 정확히 한 route에만 나온다면, 그 구획이 곧 그 route입니다.


Zone은 언제나 클라이언트 컴포넌트입니다. 모든
.Zone.tsx는 1행이 "use client"입니다. 이것은 허가가 아니라 비용입니다. 안에 남긴 JSX 엘리먼트는 HTML로 한 번, 브라우저가 다시 실행하는 번들 JS로 한 번, 모두 두 번 전송됩니다.마크업은 담지 않는다
model module의 Zone은 store를 읽고, 그리기는 같은 폴더의 서버 컴포넌트인 View에 맡깁니다. service module에는 View가 없으므로, 그리기를 맡길 컴포넌트는
ui/에 둡니다.아래 Zone은 Zone이 할 수 있는 두 가지를 모두 합니다. store state를
ui/ 컴포넌트에 prop으로 건네고, 서버 콘텐츠는 slot으로 돌려받습니다:apps/koyo/lib/_receipt/Receipt.Zone.tsx
- store 읽기 둘, 감싸는 엘리먼트 하나. Zone이 직접 그리는 것은
<section>뿐입니다. 사용자가 보는 것은ReceiptPreview와headerslot에 있습니다. - 번들에서 빠지는 것은 slot뿐입니다.
header는 page가 서버에서 그려 완성된 채로 넘깁니다.ReceiptPreview는"use client"가 없지만, Zone이 import하므로 그 코드는 Zone의 JS 청크에 함께 실립니다. - children보다 이름 있는 slot을 씁니다. slot이 있으면 page가 서버 콘텐츠를 익명 자리 하나가 아니라 이름 붙은 자리에 놓을 수 있습니다.
Layout.Navbar가title,back,left,right,children다섯 개를 받는 이유입니다.
경계를 작게 유지하는 법
- UI가 아니라 인터랙션을 감쌉니다. 쓸모 있는 가장 작은 클라이언트 컴포넌트는 동작 하나만 더하고
children은 그대로 그립니다. 그러면 안쪽 마크업은 번들에 들어가지 않습니다. - 경계를 말단까지 내립니다. key 셋을 읽고 엘리먼트 마흔 개를 그리는 Zone은, key 셋을 읽는 Zone과 마흔 개를 그리는 서버 컴포넌트로 나눕니다.


model이 아니라 id를 받습니다. Zone의 prop으로
cnst model을 넘기면 경계를 넘으며 method를 잃고, model 타입을 걸친 평범한 객체로 도착합니다. orderId: string을 받고 model은 store에서 읽으세요.데이터는 route에서 채운다
마운트될 때 불러오고 싶은 마음을 참으세요.
useEffect(…, [])는 빈 껍데기를 그리고 hydrate한 뒤에야, 서버가 첫 바이트 전에 답할 수 있었던 질문을 서버에 던집니다. akan quality ssr은 이것을 akan.ssr.client-mount-load로 알려 줍니다.대신 page가 fetch하고 await한 뒤, Zone은 그 결과를 prop으로 받습니다:
apps/koyo/page/receipt/_index.tsx
- 불러오기는 page가 합니다.
fetch.listReceiptTemplates()는 첫 바이트 전에 끝나므로, 콘솔은 이미 채워진 채로 도착합니다. - 클라이언트 컴포넌트는
fetch.*를 부르지 않습니다. 읽기는st.use.*, 쓰기는st.do.*로 합니다. - 특히
fetch.init*는 부르지 않습니다. 이것은Load.*의initprop만 읽는 hydration 스냅샷입니다. 클라이언트에서 부르면 왕복 두 번을 더 들여 아무도 읽지 않는 값을 받습니다.
데이터가 오는 곳
데이터Zone에 닿는 방법
바로 필요한 서버 데이터
page에서 await하고 prop으로 내려보냅니다.
기다려도 되는 서버 데이터
await하지 않은 promise를
<Load.Stream of={…}>에 넘깁니다. 자기 경계 뒤에서 채워집니다.클릭이 요청하는 것
st.do.*로 부르는 store action입니다.아니면 그냥 page를 쓴다
libs/shared의 _oauth가 따라 할 만한 예입니다. 동의 화면, 승인·거부 버튼, 연결된 앱 목록이 필요했지만 Util도 Zone도 없습니다._oauth에 필요했던 것해결한 방법
동의 화면
route 하나입니다:
libs/shared/page/oauth/consent/_index.tsx승인 · 거부 버튼
쿠키 세션이 인증하는 평범한
<form method="post">입니다.연결된 앱 목록
앱이 자기 page에서 부르는 endpoint
listOAuthConnections입니다.module이 내보내는 것
endpoint 10개, 컴포넌트 0개입니다.
- form post에는 스크립트가 필요 없습니다. 동의 화면은 HTML로 전송되고, 어떤 번들이 도착하기 전에도 동작합니다.
- 이 화면에서는 그것이 핵심입니다. 다른 애플리케이션이 내 이름으로 행동하도록 허락하는 화면이라, 스크립트 없이 동작하는 것은 최적화가 아니라 목표입니다.
- 인자는 경로에 실립니다. 두 endpoint 모두
.param("requestId", String)을 받습니다. 각 form은 API prefix 아래approveOAuthConsent/<requestId>나denyOAuthConsent/<requestId>로 post합니다.
route는 lib에도 둘 수 있다
lib는 module뿐 아니라 route도 가질 수 있습니다.
libs/<lib>/page는 앱의 page/와 같은 규칙을 따르고, 앱은 akan.config.ts의 syncPageLibs로 가져옵니다:apps/koyo/akan.config.ts
syncPageLibs 값가져오는 route
true
앱이 의존하는 lib 중
page 폴더가 있는 것 전부입니다.["shared"]
나열한 lib만 가져옵니다.
false
기본값입니다. lib route를 가져오지 않습니다.
lib route는 경로가 바뀌지 않습니다.
libs/shared/page/oauth/consent/_index.tsx는 이 route를 가져온 모든 앱에서 /oauth/consent로 열립니다.