webkit 개요

webkit/에는 여러 화면이 함께 쓰지만 컴포넌트는 아닌 브라우저 코드를 둡니다. 서버 전용 코드를 두는 srvkit/의 브라우저 쪽 짝입니다.
덕분에 페이지와 컴포넌트는 로직을 직접 들고 있지 않고, webkit barrel에서 이름 하나만 가져다 씁니다.
이 페이지에서 쓰는 말
barrel
폴더 안의 모든 파일을 다시 export하는 index.ts입니다. 쓰는 쪽은 폴더 경로 하나로 import합니다.
"use client"
파일을 클라이언트 코드로 표시하는 첫 줄입니다. 서버 컴포넌트는 이 파일의 함수를 호출하거나 값을 읽을 수 없습니다.
서버 컴포넌트
page, Unit, View입니다. 서버에서 그려져 브라우저에는 HTML로만 도착합니다.
어느 폴더에 둘까
폴더는 코드의 용도가 아니라 코드가 건드리는 것으로 고릅니다. common/, webkit/, srvkit/ 문서가 모두 이 표로 시작합니다.
common/
서버와 클라이언트 양쪽에서 도는 순수 코드이며 외부 의존성이 없습니다. 같은 폴더의 common/*과 akanjs/base만 import하고, Err는 쓸 수 없습니다.
webkit/
window, navigator, 네이티브 브리지(akanjs/client/native)를 건드리거나 React hook인 코드입니다.
srvkit/
node:*, Bun, process.env, 비밀값, 서버 SDK 중 하나라도 건드리는 코드입니다.
ui/
JSX를 그리거나 레시피로 모양을 정의하되 모델 하나에 묶이지 않는 코드입니다. 모델에 묶인 컴포넌트는 그 모듈에 둡니다.
plugin/
빌드나 CLI 실행 때 도는 AkanPlugin입니다. akan.config.ts에 등록합니다.

webkit에 두는 것

여기에는 다섯 종류의 코드를 둡니다. 파일 첫 줄에 "use client"가 있는지에 따라 쓸 수 있는 쪽이 정해집니다:
종류
서버
클라이언트
"use client" 없음 — 서버와 클라이언트 모두
표시용 맵
✓
✓
상태별 배지 variant 이름이나 아이콘처럼, enum 값을 고르는 공용 표입니다. 클래스 문자열은 담지 않습니다.
계정·라우팅 헬퍼
✓
✓
로그인한 계정을 읽고 비로그인 사용자를 돌려보냅니다. _layout.tsx의 getSelf가 그 예입니다.
"use client" 있음 — 클라이언트 컴포넌트에서만
브라우저 헬퍼
✓
텍스트 복사, 파일 다운로드, 쿠키 읽기, 공유 링크 열기 같은 작은 브라우저 동작입니다.
웹 hook
✓
뷰포트, 권한, 알림, 메시징 같은 브라우저 API를 감싼 재사용 hook입니다.
외부 라이브러리 래퍼
✓
브라우저용 외부 패키지를 직접 만든 함수 뒤에 숨겨, 페이지가 그 패키지를 import하지 않게 합니다.
✓사용 가능사용 불가
표시용 맵
여러 모듈이 함께 쓰는 상태별 배지 표입니다:
apps/koyo/webkit/icecreamOrderStatusVariant.ts
  • 두 번째 모듈이 쓸 때 옮깁니다. 한 모듈만 쓰는 표는 그 파일 최상단의 as const 맵으로 둡니다. 다른 모듈도 필요해지면 webkit/으로 옮깁니다.
  • 클래스 문자열은 레시피에 둡니다. 값이 클래스인 표는 variant 축이므로 여기가 아니라 ui/Recipe/의 레시피로 옮깁니다.
  • 글자는 dictionary에서 가져옵니다. 맵에는 모양만 두고, 글자는 l("icecreamOrderStatus.active")로 그립니다. 사용자가 읽는 문구는 하드코딩하지 않습니다.
  • satisfies가 모든 상태를 확인합니다. enum에 상태를 추가하고 여기에 variant를 빠뜨리면 타입 에러가 납니다.
계정·라우팅 헬퍼
로그인한 계정을 읽고, 로그인하지 않은 사용자는 로그인 페이지로 보내는 헬퍼입니다:
apps/koyo/webkit/getSignedInUser.ts
  • "use client"가 없습니다. _layout.tsx가 서버에서 호출하므로, 로그인하지 않은 사용자는 HTML이 나가기 전에 로그인 페이지로 이동합니다.
  • router.replace가 아니라 router.redirect를 씁니다. redirect는 서버와 브라우저 양쪽에서 동작하지만, replace는 브라우저 탭만 옮깁니다. 서버에서 부르면 로그아웃 상태 그대로 페이지가 그려집니다.
  • libs/shared에 이미 있습니다. 앱이 그 lib을 쓴다면 @libs/shared/webkit의 getSelf({ unauthorize: "/signin" })와 getMe를 씁니다.
브라우저 헬퍼
페이지마다 반복될 브라우저 동작 하나를 함수로 둡니다:
apps/koyo/webkit/copyText.ts
  • navigator를 건드리므로 "use client"가 붙습니다. onClick 핸들러처럼 클라이언트 컴포넌트에서만 부를 수 있습니다.
  • 먼저 @libs/shared/webkit을 확인하세요. downloadFile, JSON 내보내기용 downloadData, 파일을 올리고 준비될 때까지 기다리는 addFileUntilActive가 이미 있습니다.
웹 hook
브라우저 이벤트를 구독하고, 끝나면 스스로 정리하는 hook입니다:
apps/koyo/webkit/useViewportWidth.tsx
  • 튜플이 아니라 이름 있는 객체를 반환합니다. 호출하는 쪽은 const { width } = useViewportWidth()로 씁니다.
  • 이미 있는 hook부터 확인하세요. akanjs/webkit에는 useDebounce, useThrottle, useInterval, useEscapeKey가 있고, useCamera, useGeoLocation으로 네이티브 런타임과 브라우저 API를 감싸 둡니다. @libs/util/webkit에는 usePushNotification, useSpeech가 더 있습니다.
  • 진짜 상태가 필요할 때만 씁니다. 너비로 마크업을 보이고 숨기기만 한다면 md:hidden 같은 반응형 class로 충분하고, 두 경우 모두 서버에서 그려집니다.
외부 라이브러리 래퍼
libs/shared의 downloadFile은 file-saver를 감쌉니다:
libs/shared/webkit/downloadFile.ts
  • 페이지와 모듈 파일은 외부 패키지를 import하지 않습니다. page/**, *.Zone.tsx, *.store.ts에서 외부 패키지를 import하면 lint 에러가 나므로, 대신 downloadFile을 import합니다.
  • 외부 컴포넌트를 감쌀 때는 ui/에 둡니다. webkit/은 함수, hook, 맵처럼 camelCase 이름만 export합니다. qrcode.react를 감싼 libs/util/ui/QRCode.tsx처럼, 컴포넌트 래퍼는 ui/의 PascalCase 파일로 둡니다.

barrel과 파일 이름

webkit/은 ui/처럼 barrel 폴더라서, 모든 파일이 진입점 하나에서 다시 export됩니다. 파일 이름은 그 파일이 export하는 이름 하나를 따릅니다:
use<Thing>.tsx
React hook입니다. JSX가 없어도 확장자는 .tsx입니다.
<camelName>.ts
나머지 헬퍼, 래퍼, 맵입니다. 파일 하나에 export 하나를 두고, 이름은 파일명과 같게 합니다.
컴포넌트는 헬퍼를 barrel 경로로 가져옵니다:
apps/koyo/ui/DownloadButton.tsx
  • barrel 경로만 씁니다. @libs/shared/webkit이나 @apps/koyo/webkit으로 씁니다. @libs/shared/webkit/downloadFile처럼 더 깊은 경로는 페이지와 모듈 파일에서 lint 에러가 납니다.
  • 클릭은 버튼이 맡습니다. onClick 때문에 "use client"가 필요하고, Button은 반환된 promise가 끝날 때까지 스피너를 보여 줍니다.
  • 페이지는 file-saver를 모릅니다. 페이지는 <DownloadButton />만 그리고, 외부 패키지는 webkit/ 뒤에 남습니다.

실전 규칙

webkit 파일은 거의 다 이 여섯 가지 규칙으로 정리됩니다:
  • 로직은 webkit/, 컴포넌트는 ui/. 웹 렌더링에 필요하지만 그 자체가 재사용 UI 컴포넌트는 아닌 로직을 여기에 둡니다.
  • 브라우저 코드는 webkit/, 서버 전용 코드는 srvkit/. node:*, Bun, process.env, 비밀값을 건드리면 srvkit/에 둡니다.
  • barrel에서 import합니다. 안쪽 경로가 아니라 @libs/shared/webkit에서 가져옵니다.
  • 파일 이름과 export 이름을 맞춥니다. downloadFile.ts는 downloadFile을 export합니다.
  • "use client"는 브라우저가 필요한 곳에만 붙입니다. hook, 브라우저 API, 브라우저용 패키지에는 필요하고, 서버 컴포넌트가 읽는 파일에는 붙이면 안 됩니다.
  • //! 주석을 쓰지 않습니다. Bun이 압축 뒤에도 남겨 두기 때문에 모든 방문자에게 그대로 전송됩니다. 대신 // FIXME:를 씁니다.
webkit/을 import할 수 있는 곳
서버 파일과 공유 파일은 import type으로 webkit의 타입만 가져올 수 있고, 값은 import할 수 없습니다:
import하는 파일
값
타입
화면 쪽 파일
page/ · ui/
✓
✓
import할 수 있습니다. 단, 서버 컴포넌트는 "use client"가 없는 export만 호출합니다.
<Model>.*.tsx · *.store.ts
✓
✓
모듈 컴포넌트와 store는 @libs/util/webkit 같은 barrel에서 import합니다.
서버 파일과 공유 파일
*.service.ts · *.document.ts
✓
서버 코드는 DOM이 없는 Bun에서 돌기 때문에 webkit의 타입만 가져올 수 있습니다.
*.signal.ts · *.dictionary.ts
✓
계약 파일도 서버에서 로드되므로 같은 규칙을 따릅니다.
srvkit/
✓
서버 전용 헬퍼와 어댑터는 브라우저 코드를 불러오지 않습니다.
common/ · *.constant.ts
✓
공유 파일은 양쪽에서 돌기 때문에 webkit/에도 srvkit/에도 닿지 않습니다.
✓허용lint 에러
함께 볼 문서

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

내 AI에 이 문서 연결하기

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