사람함께에이전트▾
사람 — 직접 정하고 책임지는 비즈니스 규칙과 흐름. 직접 읽어보세요.
함께 — 개념은 알아두고, 세부 규칙은 에이전트가 따릅니다.
에이전트 — 에이전트가 따르는 규칙과 레퍼런스. 필요할 때 찾아보세요.
CLI 레퍼런스▾
AkanJS 레퍼런스▾
UI 레퍼런스▾

akanjs/client

akanjs/client는 route 파일과 클라이언트 코드가 프레임워크에서 가져다 쓰는 모듈입니다. route chain, 페이지 이동, class 병합, 인증 helper, 기기 접근, 폰트 선언이 들어 있습니다.
import { cn, page, router } from "akanjs/client";
pagelayoutrootLayout
route chain입니다. 모든 route 파일이 default export 하나로 내보내는 것입니다.
PageConfig
.config()가 받는 설정입니다. transition, safe area, cache, SSR 방식을 정합니다.
router
route 사이를 이동합니다. locale과 basePath는 알아서 붙습니다.
cn
class 이름을 합치고 Tailwind 충돌을 정리합니다.
ModelPropsModelsProps
레코드 하나 또는 목록을 그리는 컴포넌트의 props 타입입니다.
usePagemsgErrfetchsig
앱의 타입이 붙지 않은 런타임 프록시입니다. 타입이 붙은 것은 @apps/<app>/client에서 import합니다.
getCookiegetAccountgetAuthToken
cookie, 인증 토큰, 로그인한 계정을 읽습니다.
setAuthinitAuthresetAuth
fetch, cookie, storage에 있는 인증 토큰을 한 번에 저장하거나 지웁니다.
resolveServerUrl
CSR 페이지가 데스크톱 앱처럼 다른 곳에서 뜰 때, 저장된 /api/… URL을 서버 origin의 URL로 바꿉니다.
Device
기기의 platform, safe area, 키보드, 햅틱, 스크롤을 다룹니다.
Font
rootLayout().fonts([...])에 넣는 폰트 항목 하나의 타입입니다.
resolveRouteModuleisRouteDefinition
route loader가 쓰는 함수입니다. 앱 코드에서는 호출하지 않습니다.
  • 대부분은 서버에서도 동작합니다. page(), cn, getCookie, getAccount는 서버 컴포넌트에서 쓸 수 있고, router.back(), setCookie, Device는 브라우저가 필요합니다.
  • 타입이 붙은 helper는 앱에서 가져옵니다. 여기의 usePage, fetch, msg, Err, sig는 앱의 타입을 모릅니다. 앱의 dictionary key와 endpoint가 타입으로 붙은 @apps/<app>/client에서 import합니다.

router

router는 store, 이벤트 핸들러, 유틸리티에서 route 사이를 이동할 때 씁니다. 앱 안의 경로만 넘기면 locale과 basePath는 알아서 붙입니다.
push(href, { scrollToTop })
route로 이동하고 history 항목을 하나 추가합니다. 브라우저에서만 되며, 서버에서는 redirect()를 씁니다.
replace(href)
현재 history 항목을 바꾸면서 이동합니다. 브라우저에서만 되며, 서버에서는 redirect()를 씁니다.
back()
한 단계 뒤로 갑니다. 브라우저에서만 됩니다.
backOrFallback(href?)
뒤로 갈 기록이 있으면 뒤로 가고, 없으면 href로 교체합니다. 기본값은 index 경로입니다.
refresh()
현재 route를 다시 렌더링합니다. 브라우저에서만 됩니다.
redirect(href, { method, status })
서버에서는 redirect로 응답하고(기본 307), 브라우저에서는 그냥 이동합니다.
notFound()
가장 가까운 layout의 .notFound() 화면을 404와 함께 보여 줍니다. 페이지가 스트리밍을 시작한 뒤에 부르면 화면은 그대로 나오지만 브라우저는 200 상태를 받고, 크롤러와 ssr: "block" route는 404를 받습니다.
setLang(lang)
같은 route에 머문 채 locale만 바꿉니다. 브라우저에서만 됩니다.
getPath()
locale과 basePath를 뺀 현재 route입니다. 브라우저에서만 됩니다.
getPrefixedPath(path)
앱에 basePath가 있으면 경로 앞에 locale과 basePath를 붙입니다.
navigation()
마지막 push·replace를 promise로 돌려줍니다. route가 이동을 거부하면 reject됩니다.
방금 만든 레코드의 페이지로 이동하는 store action입니다:
apps/myapp/lib/project/project.store.ts
  • 경로는 앱 안의 경로로 씁니다. /en/profile이 아니라 /profile로 씁니다. locale로 시작하는 경로를 넘겨도 됩니다.
  • page에서는 redirect를 씁니다. page의 render 안에서 router.redirect("/signin")을 부르면 307 redirect로 응답합니다.

cn

cn은 class를 합치는 유일한 함수입니다. class 문자열을 이어 붙이고, Akan 시맨틱 토큰까지 포함해 Tailwind 충돌을 정리합니다.
조건부 class 하나를 더하고, 부모의 className을 맨 뒤에 받는 컴포넌트입니다:
apps/myapp/ui/Chip.tsx
  • 조건이나 병합이 있을 때만 씁니다. 고정된 class는 그냥 문자열로 둡니다. 조건부 조각이 있거나 부모가 className을 넘길 때 cn을 씁니다.
  • 부모의 class는 맨 뒤에 둡니다. 충돌하면 뒤의 class가 이기므로, 넘겨받은 className이 기본값을 덮을 수 있습니다.
  • 시맨틱 토큰도 충돌을 정리합니다. 색상·radius 토큰이 등록되어 있어서 cn("bg-primary", "bg-open")은 bg-open만 남깁니다.
  • 객체 문법은 쓰지 않습니다. { x: cond } 대신 cond && "x"로 씁니다. clsx나 twMerge를 직접 쓰지 않습니다.

ModelProps / ModelsProps

ModelProps는 레코드 하나를 그리는 Unit의 props 타입입니다. ModelProps<"user", cnst.LightUser>로 쓰면 레코드가 user prop으로 들어옵니다. ModelsProps는 목록을 그리는 컴포넌트의 props 타입입니다.
ModelProps<"user", cnst.LightUser>
usercnst.LightUser필수
그릴 레코드입니다. 첫 번째 타입 인자로 정한 이름의 prop으로 들어옵니다.
classNamestring
부모가 넘기는 class입니다.
hrefstring
카드를 눌렀을 때 이동할 경로입니다.
onClick(model) => unknown
클릭하면 레코드를 인자로 호출됩니다.
sliceSliceMeta
레코드를 가져온 slice입니다.
actionsDataAction[]
행 동작으로 보여 줄 "edit", "view", "remove" 또는 엘리먼트입니다.
columnsDataColumn[]
레코드를 표의 행으로 그릴 때 보여 줄 열입니다.
ModelsProps<cnst.LightUser>
initFetchInitForm
목록을 불러오는 설정입니다. page, limit, sort, insight 등을 담습니다.
queryQuerySetting
어떤 filter로 목록을 만들지 { queryKey, args }로 정합니다.
sliceSliceMeta
목록을 가져온 slice입니다.
onClickItem(model) => unknown
클릭한 레코드를 인자로 호출됩니다.
classNamestring
부모가 넘기는 class입니다.
레코드 페이지로 링크하는 Unit 카드입니다:
apps/myapp/lib/user/User.Unit.tsx
  • Unit과 View는 model을 받습니다. 서버 컴포넌트라서 cnst model을 prop으로 받아도 브라우저로 넘길 일이 없습니다.
  • Util과 Zone은 id를 받습니다. 클라이언트 컴포넌트이므로 userId: string을 받고 model은 store에서 읽습니다. 요즘 Zone은 init prop을 akanjs/fetch의 ClientInit으로 타입을 붙입니다.

page / layout / rootLayout

모든 route 파일의 export는 하나뿐입니다. page(), layout(), rootLayout() 중 하나로 시작해 .render()로 끝나는 chain입니다. route 설정은 모두 그 chain의 stage 하나입니다.
  • page()는 page 파일, 즉 <name>.tsx나 _index.tsx에 씁니다.
  • layout()은 _layout.tsx에 씁니다.
  • rootLayout()은 앱이나 basePath의 최상위 _layout.tsx에 씁니다.
chain별 stage
stage
page
layout
rootLayout
모든 chain
.param(name, Type)
✓
✓
✓
경로의 [name] segment 하나를 선언합니다. page는 자기 경로의 segment를 모두 선언합니다.
.search(name, Type)
✓
✓
✓
query key 하나를 선언합니다. [Type]으로 쓰면 목록이 됩니다.
.config(options)
✓
✓
✓
PageConfig로 transition, safe area, cache, SSR 방식을 정합니다.
.head(node | fn)
✓
✓
✓
<title>, <meta>, <link> 태그를 JSX로 적습니다.
.loading(fn)
✓
✓
✓
render가 기다리는 동안 보여 줍니다. path 값은 받지만 search 값은 받지 않습니다.
.render(fn)
✓
✓
✓
마지막 stage입니다. 타입이 맞춰진 인자로 route를 그립니다.
page 전용
.prompt(name, desc)
✓
화면을 MCP prompt로 공개합니다.
layout 전용
.notFound(fn)
✓
✓
하위 경로에서 페이지를 찾지 못했을 때 보여 줄 화면입니다.
.error(fn)
✓
✓
하위 route를 렌더링하다 오류가 나면 보여 줄 화면입니다.
root layout 전용
.fonts(Font[])
✓
빌드가 subset하고 preload할 폰트입니다.
.theme(name)
✓
페이지가 처음 열릴 때의 테마입니다.
.manifest(obj)
✓
web app manifest입니다.
.layoutStyle(…)
✓
창을 꽉 채우는 web 레이아웃과 가운데 폰 화면 열 중에서 고릅니다.
.reconnect(on?)
✓
연결이 끊겼을 때 뜨는 오버레이입니다.
.wsConnect(on?)
✓
페이지가 열릴 때 websocket을 연결합니다.
✓쓸 수 있음쓸 수 없음
render가 받는 값
선언한 인자는 타입이 맞춰진 값으로 들어옵니다:
선언받는 값
↳ 참고
ID · Stringstring
Int · Floatnumber
Booleanboolean
DateDayjs
day.js 날짜 값입니다.
cnst.TicketStatus"open" | …
enumOf 클래스는 값 union으로 옵니다.
[String]string[]
search 전용입니다. key를 반복해 씁니다: ?tags=a&tags=b.
path segment 하나와 query key 둘을 읽는 page입니다:
apps/myapp/page/project/[projectId]/_index.tsx
  • search 값은 모두 optional입니다. 없거나 읽을 수 없으면 undefined로 옵니다. path 값이 타입에 맞지 않으면 not-found로 응답합니다.
  • lang은 늘 들어옵니다. 모든 route는 locale segment 아래에 있으므로 모든 stage가 lang을 문자열로 받습니다. .param()으로 선언하지 않습니다.
  • render callback은 React 컴포넌트가 아닙니다. 그 안에서 usePage(), getSelf(), fetch.*를 부르고, await할 때만 async를 붙입니다.
  • 이름은 문자열 리터럴로 씁니다. [projectId] 폴더에는 그 이름 그대로 .param("projectId", ID)가 있어야 합니다. .prompt("name", …)도 같습니다.

layout / rootLayout 전용 stage

layout()은 .prompt()를 뺀 page()의 stage를 모두 받고, render에 children도 함께 받습니다. 자기가 읽는 [x] segment만 선언해도 됩니다.
layout()이 더하는 것
.notFound(fn)({ pathname, params, searchParams }) => node
하위 경로에서 페이지를 찾지 못했을 때의 화면입니다. 예전 NotFound export를 대신합니다.
.error(fn)({ error, digest, pathname }) => node
하위 route 렌더링 중 오류가 났을 때의 화면입니다. 예전 Error export를 대신합니다.
rootLayout()이 더하는 것
앱 전체에 걸리는 설정이라서 앱이나 basePath의 최상위 _layout.tsx에서만 정합니다.
.fonts(fonts)Font[]
subset하고 preload할 폰트입니다. 목록은 인라인으로 씁니다. 아래 Font / createFont를 보세요.
.theme(theme)"system" | "css" | string
system은 OS 설정을 따르고, css는 data-theme를 달지 않으며, 그 밖의 이름은 그대로 답니다.
.manifest(manifest)WebAppManifest
PWA manifest입니다. data URL로 내보냅니다.
.layoutStyle(style)"web" | "mobile"기본값 "web"
mobile은 앱을 가운데 최대 600px 폭의 열에 그리고, 그보다 좁은 화면은 꽉 채웁니다.
.reconnect(on = true)boolean기본값 operationMode === "local"
websocket 연결이 끊긴 동안 오버레이를 띄웁니다.
.wsConnect(on = true)boolean기본값 true
페이지가 열리면 websocket을 연결합니다. false라면 구독 전에 fetch.instance.connect()를 부릅니다.
앱의 root layout입니다. import "./styles.css";는 첫 줄에 그대로 둡니다:
apps/myapp/page/_layout.tsx
segment 하나를 읽고, 하위 경로의 not-found 화면을 정하는 중첩 layout입니다:
apps/myapp/page/org/[orgId]/_layout.tsx
  • theme cookie가 우선입니다. 사용자가 테마를 고르면 다음부터는 theme cookie가 .theme()보다 앞섭니다.
  • 인자가 없으면 켜짐입니다. 인자 없이 부른 .reconnect()와 .wsConnect()는 true입니다.

PageConfig

PageConfig는 .config()가 받는 객체입니다. route가 어떻게 들어오는지, 기기 가장자리에 여백을 얼마나 두는지, 서버가 어떻게 보내는지를 정합니다.
transition"none" | "fade" | "bottomUp" | "stack" | "scaleOut"기본값 플랫폼별
들어올 때의 애니메이션입니다. 하위 route는 iOS에서 stack, Android에서 scaleOut, 그 밖에는 none입니다.
safeAreaboolean | "top" | "bottom" | { top, bottom, android }기본값 앱은 켜짐, 웹은 꺼짐
노치와 홈 바만큼 페이지에 여백을 줍니다.
topInsetnumber | boolean기본값 0
고정된 상단 바를 위해 비워 둘 높이(px)입니다. true는 48입니다.
bottomInsetnumber | boolean기본값 0
고정된 하단 바를 위해 비워 둘 높이(px)입니다. true는 48입니다.
gestureboolean기본값 iOS의 하위 route에서만 켜짐
밀어서 뒤로 가기를 허용합니다.
cacheboolean기본값 최상위 route는 true
앱 셸에서 페이지의 마지막 렌더를 보관했다가 돌아오면 다시 보여 줍니다.
ssr"stream" | "block"기본값 "stream"
stream은 셸을 먼저 보내고, block은 모든 섹션을 기다린 뒤 첫 바이트를 보냅니다.
topSafeAreaColorstring기본값 배경색
상단 safe area 뒤에 칠할 색입니다.
bottomSafeAreaColorstring기본값 배경색
하단 safe area 뒤에 칠할 색입니다.
devOnlyboolean기본값 false
route를 akan build에서 뺍니다. akan start에서는 계속 열립니다.
아래에서 올라오고, 노치만큼 여백을 두며, 운영 빌드에는 들어가지 않는 playground page입니다:
apps/myapp/page/playground.tsx
  • 설정은 트리를 따라 합쳐집니다. layout의 설정은 그 아래 모든 route에 적용되고, page에 직접 적은 값이 이깁니다.
  • devOnly는 리터럴로 씁니다. true나 false를 그대로 적습니다. _layout.tsx에 두면 그 폴더 아래 route가 모두 빠집니다.
  • block은 속도 대신 깔끔한 오류 화면을 택합니다. ssr: "block"이면 Loading 화면이 브라우저에 가지 않습니다. SEO와 첫 화면이 중요하지 않은 route에만 씁니다.
  • 크롤러는 언제나 완성된 페이지를 받습니다. 검색엔진, AI 크롤러, 링크 미리보기는 ssr 값과 상관없이 모든 섹션이 제자리에 들어간 HTML을 받습니다. 스트리밍된 섹션을 드러낼 스크립트를 실행하지 않기 때문입니다.

prompt

.prompt(name, description)은 page를 MCP prompt로 공개해서, 에이전트가 사람이 보는 화면을 그대로 열 수 있게 합니다. description은 model이 받는 지시문 전체이며 영어로 씁니다.
.prompt(name, …)
prompt 이름입니다. 영문자, 숫자, _, -만 쓰고 64자까지입니다.
.prompt(…, description)
model이 받는 지시문 전체입니다. 영어로, 비워 두지 않고 씁니다.
.param(name, Type, { desc })
필수 prompt 인자가 됩니다. desc를 적어 주세요.
.search(name, Type, { desc })
선택 prompt 인자가 됩니다. 목록은 쉼표로 구분해 입력합니다.
티켓 보드를 prompt로 공개하는 page입니다:
apps/myapp/page/project/[projectId]/board.tsx
에이전트가 받는 답
prompts/get은 호출자의 token으로 page body를 실행하고, 화면은 그리지 않습니다. 실행 결과에 따라 이렇게 답합니다:
상황
↳ prompts/get의 답
page가 정상 실행됨
description, fetch.* query마다 resource 하나, 그리고 Tools for this screen: … 한 줄입니다.
필수 인자가 빠짐
빠진 인자마다 메시지 하나로, id를 찾을 <model>List… tool을 알려 줍니다.
redirect나 guard 거절, token 없음
401 challenge입니다. 클라이언트가 먼저 로그인하게 합니다.
redirect나 guard 거절, token 있음
This screen is not available to the signed-in account.
page가 not-found로 응답함
No screen exists for these arguments.
  • 데이터는 마스킹됩니다. resource마다 endpoint의 return model로 마스킹되고 akan:// uri로 주소가 붙습니다. 같은 도큐먼트는 여러 query가 읽어도 한 번만 갑니다.
  • 잘리는 것은 목록뿐입니다. 목록은 promptBudget자(기본 60,000자)에 맞게 잘립니다. option.setMcp({ promptBudget })나 AKAN_MCP_PROMPT_BUDGET으로 바꿉니다.
  • tool은 그 화면의 것만 알려 줍니다. 마지막 줄에는 page가 데이터를 가져온 모듈의 공개 tool 중 호출자가 볼 수 있는 것만 나옵니다.

resolveRouteModule / isRouteDefinition

모든 route loader는 이 두 함수로 route 파일을 읽습니다. 앱 코드에서는 부르지 않고, route 파일을 직접 불러오는 도구를 만들 때만 씁니다.
resolveRouteModule(mod, key, { kind, pattern })
chain의 default export를 named-export 모양으로 펼칩니다. legacy module은 그대로 통과합니다.
isRouteDefinition(value)
값이 page(), layout(), rootLayout() chain이면 true입니다.
서버와 같은 방식으로 route 파일 하나를 불러오는 스크립트입니다:
apps/myapp/script/inspectRoute.ts
  • 두 모양을 함께 돌려줍니다. module은 loader가 읽는 모양이고, definition은 chain module일 때만 있습니다.
  • 파일을 검사합니다. chain 옆의 named export는 언제나 거절합니다. kind를 넘기면 파일 종류와 맞지 않는 chain을, pattern을 넘기면 경로와 맞지 않는 .param()을 함께 거절합니다.

Font / createFont

Font는 rootLayout().fonts([...])에 넣는 항목 하나의 타입입니다. 빌드가 폰트마다 subset을 만들고, /_akan/fonts에서 제공하며, preload합니다.
namestring필수
폰트 family 이름입니다. --font-<name> 변수와 font-<name> class의 이름도 됩니다.
paths{ src, weight, style? }[]필수
굵기·스타일마다 파일 하나입니다. src는 /로 시작하고 public/에서 읽습니다.
defaultboolean
앱 전체에 이 폰트를 적용합니다. root layout마다 하나까지만 됩니다.
subsetsstring[]기본값 ["latin"]
남길 문자 집합입니다. 예를 들어 latin, 한국어는 ks-x-1001입니다.
subsetfalse
subset을 건너뜁니다. 파일을 woff2로 바꾸기만 합니다.
optimizeboolean기본값 true
false면 빌드 단계와 preload 없이 src 파일을 그대로 씁니다.
preloadboolean기본값 true
최적화된 파일마다 preload 링크를 답니다.
display"auto" | "block" | "swap" | "fallback" | "optional"기본값 "swap"
CSS font-display 값입니다.
variablestring기본값 --font-<name>
폰트 family를 담는 CSS 변수 이름입니다.
classNamestring기본값 font-<name>
default 폰트가 앱에 붙이는 class 이름입니다.
두 가지 굵기의 한글 폰트를 앱 전체에 적용하는 예입니다:
apps/myapp/page/_layout.tsx
  • 경로는 public URL입니다. /fonts/NotoSansKR-Regular.woff2는 apps/myapp/public/fonts/에서 읽습니다. ./font.woff2 같은 상대 경로는 찾지 못합니다.
  • createFont는 자리만 채우는 shim입니다. createFont와 이름 붙은 factory인 Noto_Sans_KR, Inter, Roboto, Nanum_Gothic_Coding은 null을 돌려줍니다. 예전 font factory import가 깨지지 않게 둔 것이며, 폰트를 선언하지 않습니다.

usePage / msg / Err

번역, toast 메시지, 오류 클래스입니다. 앱의 dictionary로 key 타입이 붙은 @apps/<app>/client에서 import합니다.
usePage()
{ l, lang, path }를 돌려줍니다. 서버 컴포넌트에서도 됩니다.
l(key, params?)
project.name 같은 dictionary key를 번역합니다.
l.trans({ en, ko })
현재 locale의 문구를 고릅니다. 없으면 기본 locale의 문구를 씁니다.
l.rich(key)
HTML 태그가 들어 있는 번역을 그대로 렌더링합니다.
l._(key)
l과 같지만 key의 타입을 검사하지 않습니다.
msg.success(key, { key, duration, data })
dictionary key로 toast를 띄웁니다. 기본 3초이며 info, warning, error, loading도 같습니다.
new Err(key, data?)
번역되는 오류 클래스입니다. key는 <module>.error.<key> 모양이고 status는 400입니다.
Err.BadRequestErr.UnauthorizedErr.ForbiddenErr.NotFoundErr.Conflict
각자 status를 가진 하위 클래스입니다. 차례로 400, 401, 403, 404, 409입니다.
usePage()는 서버 View에서도 되므로, 번역 문구 때문에 클라이언트 컴포넌트를 만들 필요가 없습니다:
apps/myapp/lib/project/Project.View.tsx
store에서는 msg로 검사 실패를 알리고 성공을 확인해 줍니다:
apps/myapp/lib/project/project.store.ts
  • 검증 실패는 알리기만 하고 throw하지 않습니다. 클라이언트에서는 msg.error(key)를 부르고 바로 return합니다.
  • store action은 catch하지 않습니다. 실패한 fetch.*는 서버의 Err로 reject되고, 프레임워크가 그것을 toast로 띄웁니다.
  • key가 같으면 toast는 하나입니다. option.key가 같은 toast는 앞의 것을 바꾸고, data는 번역문의 매개변수를 채웁니다.

fetch / sig

fetch는 서버의 endpoint와 slice를 호출하고, sig는 model마다 signal 정보를 담아 store를 만들 수 있게 합니다. 둘 다 앱에서 import합니다. UI에서는 @apps/<app>/client, lib/ 안에서는 ../useClient입니다.
fetch.<endpoint>(...args)
생성된 endpoint나 직접 만든 endpoint 하나를 호출합니다. 예: fetch.user(id).
fetch.init<Model><Suffix>(...args)
route에서 slice의 목록과 insight를 불러와 Zone의 init prop으로 넘깁니다.
fetch.view<Model>(id)fetch.edit<Model>(id)
레코드 하나를 { project, projectView } 또는 { project, projectEdit }로 불러옵니다.
fetch.instance
클라이언트 자체입니다. setTimeout(ms), connect()가 있습니다.
sig.<model>
model의 slice와 endpoint 정보입니다. store(sig.project, …)가 이것으로 만들어집니다.
sig.project로 만든 store가 프로젝트를 보관 처리하고 목록을 갱신하는 예입니다:
apps/myapp/lib/project/project.store.ts
  • 타입은 앱에서만 붙습니다. akanjs/client의 export는 앱 client가 등록한 런타임으로 호출을 넘기지만, 앱의 타입은 없습니다.
  • 데이터는 route에서 불러옵니다. 클라이언트 컴포넌트는 st.use.*로 읽고 st.do.*로 씁니다. route가 데이터를 불러와 init이나 view로 넘깁니다.
  • 모든 호출에 token이 실립니다. setAuth 다음부터는 호출마다 JWT를 보냅니다.

getCookie / setCookie / getAccount / getAuthToken

서버든 브라우저든 어느 컴포넌트에서나 cookie와 로그인한 계정을 읽습니다. 인증 token은 앱마다 이름이 다른 cookie에 들어 있습니다.
getCookie(key)
cookie를 읽습니다. 서버와 브라우저 모두에서 됩니다.
setCookie(key, value, options?)
브라우저에 cookie를 씁니다(path=/, SameSite=Lax, Secure; options는 적은 항목만 바꿉니다). 서버에서는 아무 일도 하지 않습니다.
removeCookie(key)
브라우저에서 cookie를 지웁니다.
getHeader(key)
서버에서 요청 header를 읽습니다. 브라우저에서는 비어 있습니다.
getAuthToken()
cookie에 있는 이 앱의 JWT입니다.
getStoredAuthToken()
클라이언트 storage의 JWT입니다. 웹은 localStorage, 앱은 네이티브 런타임의 secure storage(iOS Keychain, Android Keystore)에서 읽습니다.
authTokenKey()
cookie 이름인 jwt:<appName>입니다.
getAccount<T>()
JWT를 계정으로 풀어 줍니다. 다른 앱·환경의 token이면 로그아웃 상태로 봅니다.
libs/shared의 getSelf()는 getAccount() 위에 만들어져 있습니다. 줄여 보면 이렇습니다:
libs/shared/webkit/cookie.ts
  • key가 앱마다 다른 이유. cookie에는 port가 없어서, 한 host의 두 앱이 jwt cookie 하나를 나눠 쓰게 됩니다. 이름으로 직접 읽지 말고 getAuthToken()을 씁니다.
  • getAccount는 서버가 인정하는 것만 읽습니다. 앱의 cookie나 Authorization: Bearer header를 풉니다. 서버가 받아 주는 자격 증명도 이 두 가지입니다.

setAuth / initAuth / resetAuth

이 세 함수는 fetch, cookie, 클라이언트 storage가 같은 token을 갖게 합니다. 로그인 뒤에는 setAuth를 부르고, initAuth는 프레임워크가 시작할 때 이미 부릅니다.
setAuth({ jwt })
fetch에 token을 주고 cookie와 클라이언트 storage에 저장합니다.
initAuth({ jwt? })
시작할 때 ?jwt=나 cookie에서 token을 되살립니다. 다른 앱의 token은 무시합니다.
resetAuth()
fetch, cookie, 클라이언트 storage에서 token을 모두 지웁니다. 세션을 완전히 버릴 때 씁니다.
libs/shared의 token 갱신은 서버 호출 한 번과 setAuth 한 번입니다:
libs/shared/ui/Auth/tokenRefresh.util.ts
  • 로그아웃도 token을 바꿔 끼웁니다. libs/shared의 store는 로그인 뒤에 setAuth를 부르고, 로그아웃 뒤에도 fetch.signoutUser()가 돌려준 token으로 다시 부릅니다.
  • 다른 앱의 token은 버립니다. initAuth는 다른 앱이나 환경에서 발급된 token을 무시하고, cookie에서 온 것이면 지웁니다.

Device

Device는 네이티브 런타임이 폰에서 제공하는 platform, safe area, 키보드, 햅틱, 스크롤을 감쌉니다. 프레임워크가 브라우저에서 한 번 불러 두므로 Device.getDevice()로 꺼내 씁니다.
Device.getDevice()
불러온 device를 돌려줍니다. 프레임워크가 불러오기 전에는 오류를 던집니다.
info.platform
"ios", "android", "web" 중 하나입니다.
lang
앱이 열릴 때의 locale입니다.
topSafeAreabottomSafeArea
노치와 홈 바 높이(px)입니다. 웹에서는 0입니다.
isMobile
터치 기기나 모바일 브라우저면 true입니다. isMobileDevice()도 같은 검사입니다.
vibrate(type?)
햅틱 진동입니다. "light", "medium"(기본), "heavy" 또는 ms 단위 길이를 넘깁니다.
showKeyboard()hideKeyboard()
네이티브 키보드를 열거나 닫습니다.
listenKeyboardChanged(fn)unlistenKeyboardChanged()
키보드가 열리고 닫힐 때 높이를 알려 줍니다.
getScrollTop()setScrollTop(y)
페이지의 스크롤 위치를 읽거나 바꿉니다.
동작하기 전에 가볍게 진동하는 버튼입니다:
apps/myapp/ui/HapticButton.tsx
  • 브라우저에서만 씁니다. 서버에서나 부팅 전에는 Device.getDevice()가 오류를 던집니다. 이벤트 핸들러나 effect 안에서 부릅니다.
  • 웹에서는 조용히 넘어갑니다. 웹에서는 키보드와 햅틱이 아무 일도 하지 않고 inset은 0이므로, platform을 따로 확인하지 않아도 됩니다.

MIT 라이선스 하에 배포되었습니다.

내 AI에 이 문서 연결하기

MCPhttps://akanjs.com/mcp
Copyright © 2026 Akan.js 모든 권리 보유.시스템 관리자bassman