공통 유틸리티 개요

common/에는 서버와 브라우저에서 똑같이 동작하는 코드를 둡니다. 어느 런타임에도 기대지 않는 작고 순수한 helper입니다. service, signal, store, page, *.constant.ts가 모두 같은 helper를 import해 씁니다.
포맷, 검증, 메타데이터, 변환 로직이 양쪽에 모두 필요할 때 씁니다. 한곳에 두면 서버와 브라우저의 답이 어긋나지 않습니다.
어느 폴더에 둘까
lib/ 바깥 코드는 다섯 폴더 중 하나에 둡니다. 무엇을 위한 코드인지가 아니라 무엇을 건드리는지로 고르며, srvkit/과 webkit/ 문서도 같은 표로 시작합니다.
common/
순수하고 서버·클라이언트 양쪽에서 실행되며 의존성이 없습니다. 형제 common/*과 akanjs/base만 import하고 Err는 못 씁니다.
webkit/
window, navigator, 네이티브 브리지(akanjs/client/native)를 건드리거나 React hook입니다.
srvkit/
node:*, Bun, process.env, secret, 서버 SDK 중 하나라도 건드립니다.
ui/
JSX를 그리거나 레시피로 모양을 정의하되 model 하나에 묶이지 않습니다. model에 묶인 컴포넌트는 그 module에 둡니다.
plugin/
빌드 시점이나 CLI 시점에 실행되는 AkanPlugin입니다. akan.config.ts에 등록합니다.

common/에 두는 것

여기에는 보통 다섯 종류의 helper를 둡니다. 모두 인자 말고는 필요한 것이 없어서, 어느 쪽에서 부르든 같은 답을 냅니다:
포맷터
service 응답과 UI 표시가 함께 쓰는 포맷 로직입니다. 바이트, 금액, 짧은 라벨 같은 것입니다.
검증 함수
서버와 브라우저에서 같은 답을 내야 하는 검증 함수나 판별 함수입니다.
랜덤·문자열 유틸
랜덤 코드, 자릿수 채우기, 섞기, 짧은 문자열 변환 같은 작은 범용 helper입니다.
메타데이터 빌더
query, filter, 표시 방식을 실행하지 않고 설명만 하는 작은 객체나 빌더입니다.
콘텐츠 변환
저장된 콘텐츠를 다른 모양으로 바꾸는 순수 변환입니다. rich editor JSON을 일반 텍스트로 바꾸는 것이 예입니다.
  • 둘은 예시, 셋은 실제 코드입니다. formatBytes와 isWebUrl은 예시 앱의 파일이고, 나머지 셋은 이 워크스페이스에 실제로 있는 파일입니다.
  • constant 파일은 common/은 import할 수 있지만 ui/, webkit/, srvkit/은 import할 수 없습니다. summary.constant.ts는 양쪽에서 모두 불러오므로, 여기서 부르는 getQueryMeta 빌더도 common/에 있어야 합니다.

barrel과 파일 구성

common/도 ui/, webkit/, srvkit/처럼 barrel 폴더입니다. 폴더의 index.ts가 안의 helper를 모두 다시 export하며, 한 줄씩 이렇게 생겼습니다:
libs/util/common/index.ts
그래서 쓰는 쪽은 파일 하나가 아니라 폴더를 import합니다. 경로는 @libs/<lib>/common 또는 @apps/<app>/common입니다:
libs/shared/lib/user/user.service.ts
  • 파일 하나에 export 하나. randomCode.ts는 randomCode를 export하므로, helper 이름만 알면 파일을 찾을 수 있습니다.
  • camelCase 파일 이름만 barrel에 들어갑니다. queryMeta.helper.ts처럼 점이 들어간 이름과 테스트 파일은 폴더 안에서만 씁니다.
  • 같은 폴더끼리는 상대 경로로 import합니다. randomCode.ts는 자기 폴더의 barrel이 아니라 ./pad를 import합니다.
  • index.ts는 자동으로 만들어집니다. helper 파일을 추가하거나 이름을 바꾸거나 지우기만 하고, index를 직접 고치지 않습니다.

서버와 브라우저에서 함께 쓰기

common helper는 import한 쪽에서 실행됩니다. service가 부르면 Bun에서, store나 클라이언트 컴포넌트가 부르면 브라우저에서 돕니다. 그래서 양쪽에 모두 있는 것만 쓸 수 있습니다:
helper가 쓰는 것
common/
webkit/
srvkit/
양쪽 모두에 있는 것
./<sibling> · akanjs/base
✓
같은 폴더의 파일과 akanjs/base만 값으로 import할 수 있습니다.
URL · Intl · Math · JSON
✓
표준 JavaScript 내장 객체는 Bun에도, 모든 브라우저에도 있습니다.
import type
✓
번들링 전에 지워지므로 어느 패키지의 타입이든 가져올 수 있습니다.
브라우저에만 있는 것
window · document · navigator
✓
브라우저 전역 객체는 서버에 없습니다.
akanjs/client/native · React hook
✓
네이티브 앱 브리지는 브라우저에만 있고, React hook은 클라이언트 컴포넌트에서만 씁니다.
서버에만 있는 것
node:* · fs · Bun
✓
브라우저 번들이 불러올 수 없는 서버 런타임 API입니다.
process.env · secret
✓
서버 설정과 secret은 브라우저 번들에 들어가면 안 됩니다.
서버 SDK
✓
결제, 메일, 스토리지 같은 vendor client입니다.
✓여기에 둡니다여기가 아닙니다
helper 하나를 양쪽에서
withRedirectQuery는 이미 query가 붙어 있을 수 있는 redirect URL에 param을 더합니다. URLSearchParams와 문자열 메서드만 씁니다:
libs/shared/common/redirectQuery.ts
libs/shared/lib/user/의 user 모듈은 이 helper를 서버와 브라우저 양쪽에서 부릅니다. @libs/util/common의 helper 두 개도 함께 씁니다:
파일실행 위치
↳ 호출
user.service.ts서버
withRedirectQuery(signupRedirect, { userId: user.id })
user.service.ts서버
randomCode(6)
user.store.ts브라우저
router.push(withRedirectQuery(redirect, { userId }))
User.Util.tsx브라우저
pad(phoneCodeRemain.minute, 2)
  • import는 하나입니다. service와 store가 @libs/shared/common에서 같은 이름을 import하고, 쪽마다 달라지는 것은 없습니다.
  • 양쪽이 어긋날 수 없습니다. service는 가입 redirect를, store는 다음 단계 URL을 같은 함수로 만들므로 query 형식이 항상 같습니다.

실전 규칙

helper를 어디에 둘까
  • 양쪽에 필요하면 → common/. service·signal 코드와 page·component 코드가 같은 로직을 씁니다.
  • 서버 전용 API가 필요하면 → srvkit/.
  • 브라우저 전용 API가 필요하면 → webkit/.
  • model 옆이나 base/에 두지 않습니다. helper 파일은 lib/<model>/ 안에 두지 않고, base/ 폴더도 만들지 않습니다. 공용 유틸은 그 app이나 lib의 common/에 둡니다.
common 파일 안에서
  • 작고 순수하게, barrel에서 import. helper 하나에 일 하나, 부수 효과 없이 두고, 쓰는 쪽은 폴더 경로로 import합니다.
  • throw하지 말고 값을 돌려줍니다. common/에는 Err를 import할 수 없으므로, null이나 false 같은 값을 돌려주고 판단은 service나 store에 맡깁니다.
  • //! 대신 // FIXME:를 씁니다. common/은 브라우저로 전송되는데, //! 주석은 minify 뒤에도 남습니다.

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

내 AI에 이 문서 연결하기

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