YouBothAgent▾
You — Business rules and flows you own. Read these yourself.
Both — Know the idea; your agent follows the details.
Agent — Conventions and references your agent follows. Look up as needed.
Workspace▾
App & Library▾
Domain▾
Scalar▾
akan.config.ts Overview
Every app and library keeps one
akan.config.ts at its root. It declares how that app is served, built and packaged: domains, web surfaces, the native app, the database modes and the Docker image.Start from an empty object. Every key you leave out takes a framework default, so add a key only when the default stops fitting:
apps/myapp/akan.config.ts
Every key at a glance
An app declares its config as
AppConfig and a library as LibConfig. The sections below cover each key; api, assets and plugins are in the full reference.Key
App
AppConfig
Library
LibConfig
Serving the web
routes
✓
Which domains open the app, and which basePath each one maps to.
web
✓
Which web surfaces the build produces: SSR pages, the CSR shell, or API only.
api
✓
Where endpoints and the websocket are mounted. Defaults to
/api and /ws.i18n
✓
The locales the app serves and the default one.
images
✓
Sizes, formats and allowed sources for the image optimizer.
syncPageLibs
✓
Which libraries' page folders this app serves as its own routes.
Native apps, data and env
native
✓
The iOS, Android and desktop app: its identity, platform settings and targets.
database
✓
The database modes the build can run in; a deployment picks one with
AKAN_DATABASE_MODE.publicEnv
✓
An allowlist of extra env names for browser code. The build does not read it yet.
secrets
✓
Private files that ship with
akan upload-env and stay out of git.Build and image
externalLibs
✓
✓
Packages kept out of the bundle and installed in the production image.
barrelImports
✓
Extra barrels whose imports the build rewrites to the exact file.
optimizeImports
✓
Extra packages the browser build parses only as far as they are used.
docker
✓
✓
The production image. A library adds
preRuns and postRuns only.assets
✓
✓
Which fonts the build prunes from its
public/ copy. A library sets keepFonts only.plugins
✓
✓
Akan plugins the CLI reads for runtime packages, native setup and assets.
✓Can be declaredNot accepted
Config File Shape
AppConfig and LibConfig accept a plain object or a function that returns one. Use an object unless a value depends on the app's own name:apps/myapp/akan.config.ts
The function form receives
{ name, type } of the app or library being loaded:apps/myapp/akan.config.ts
- Same keys. The function returns exactly what the object form would contain.
- Name and type only. The argument is
AppConfigContext(type: "app") orLibConfigContext(type: "lib"). - Default export. Akan reads the file's
export default; a named export is ignored.
routes
routes tells the server which domains open the app. Give each route a basePath when one app serves several clients, such as a shop and its admin:apps/shop/akan.config.ts
basePathstring
The client this route opens, with pages under
page/<basePath>. Omit it for a single client.domains{ [branch]: string[] }
Hosts that open this route, keyed by branch:
debug, develop, main or your own key.- The host picks the client. A request whose host is listed under a route is served from that route's
basePath. - Every branch gets a default host. For
debug,develop,mainand any branch key you add, each basePath also answers on<basePath>-<branch>.<AKAN_PUBLIC_SERVE_DOMAIN>. - No basePath, one client. Without any basePath the app answers on
<app>-<branch>.<AKAN_PUBLIC_SERVE_DOMAIN>and serves every page underpage/.


Declare a basePath here before the native app uses it. A
basePath in native or one of its targets must be one of the basePaths in routes.native
native defines the iOS, Android and desktop app the @akanjs/native runtime builds from this app's web surface: its name, bundle id, version and permissions, with what only one platform reads under ios, android or desktop.One native app is written straight into native. To ship several, add targets: each takes the same fields and overrides native's field by field, merging objects key by key and replacing lists and every other value. Without targets the app has one target, named default:
apps/shop/akan.config.ts
appNamestringdefault the app name
Display name of the native app.
appIdstringdefault com.<repo>.<app>
Android applicationId and iOS bundle id.
versionstringdefault 0.0.1
User-facing version: Android versionName and the iOS marketing version.
buildNumnumberdefault 1
Store build number: Android versionCode and the iOS build number.
basePathstring
The client the app opens, a basePath declared in
routes. Leave it out when the app has none.permissions("camera" | "contacts" | "location" | "push" | "speech")[]default []
Native permissions. Each one turns on the matching plugin's native setup.
targetsRecord<string, AkanNativeSettings>default { default: {} }
One entry per native app, keyed by its name. Each takes the fields of
native but targets.- More fields.
indexPath,icon,splash,plugins,deepLinks,updatesand the platform sectionsios(Info.plist, entitlements, the privacy manifest, bundle files),android(google-services.json, manifest XML, files) anddesktop(a carried server, kiosk settings) are listed in the config reference. - Pin a real appId before you ship. Placeholder ids such as
com.example.*are usually taken on Apple's portal, andakan doctor --ioswarns about them. - Keep the CSR shell on. The native app ships it, so a
nativesection cannot sit besideweb: { csr: false }. - Platform setup. Firebase files, signing and store builds are covered in Mobile Setup.


Keep signing secrets out of this file. Keystore paths and signing passwords are machine-specific, so they belong in local-only files or deployment secrets.
database
database.modes lists the database modes the app's build can run in. A mode picks the engines behind storage, the queue and the cache; most apps leave the key out and run on single:| Mode | Database, queue and cache |
|---|---|
| single | SQLite for all three, so no extra server runs. |
| multiple | One SQLite file on a host volume for data, Redis for the queue and cache. |
| cluster | Postgres for data, Redis for the queue and cache. |
Declare every mode a deployment of the app may use. The first one is the default:
apps/enterprise/akan.config.ts
- A deployment names one of them.
AKAN_DATABASE_MODEpicks one of the declared modes and no other. With one declared it may be left out; with several, every deployment names one. - The CLI picks the same way.
akan start,akan build,akan scriptandakan consoleuse the shell'sAKAN_DATABASE_MODEif set, otherwise the first declared mode. - Connection values come from the deployment.
SQLITE_DATABASE_PATH,POSTGRES_URLand the other connection variables win over the same values inenv.server.ts, andREDIS_URIis read from the environment only. - Drivers follow the declared modes.
akan buildputs every declared mode's drivers in the productionpackage.json:multipleaddsbullmqandioredis, andclusteraddspostgrestoo. - Move up only for a real need. Locally,
multipleneeds Redis andclusterneeds Redis and Postgres;akan startstarts them, andakan dbupstarts what your apps declare. When to switch is explained in Database Mode.
web
web decides which browser surfaces the build produces and the server mounts. The API is always served; only the page surfaces switch.SurfaceDescription
API
Signal endpoints and the websocket. Always served;
web does not switch it.SSR
Server-rendered pages: the route renderer, its pages and client bundles, and the RSC worker.
CSR
The single-file SPA shell that the native mobile build ships.
Value
API
SSR
CSR
What each value builds
web: true
✓
✓
✓
The default. Pages and the mobile shell, for an app that also ships a native app.
web: { csr: false }
✓
✓
Pages without the mobile shell, for a web-only app.
web: false
✓
API only. Nothing under
page/ or public/ is served, synced library routes included.✓Built and servedLeft out
A web-only app with no native build drops the mobile shell like this:
apps/myapp/akan.config.ts
- No CSR-only option. The CSR shell inlines the stylesheet the SSR build compiles, so CSR without SSR would ship an unstyled app.
- Env vars only narrow.
AKAN_SSR=falseorAKAN_CSR=falseturns a surface off for one deployment, but cannot turn on one the build left out. - Dev keeps everything.
akan startignoresweband serves every surface.


A native app needs the CSR shell. Do not combine a
native section with web: { csr: false } or web: false; drop one of the two.images
images configures the built-in image optimizer: the widths, formats and qualities it serves, and the sources it may fetch. Write only the fields you change:apps/catalog/akan.config.ts
remotePatterns{ protocol?, hostname?, port?, pathname?, search? }[]default []
Remote sources the optimizer may fetch. A host not listed is refused.
localPatterns{ pathname?, search? }[]default [{ pathname: "/**" }]
Local
public/ paths it may serve.deviceSizesnumber[]default [640, 750, 828, 1080, 1200, 1920, 2048, 3840]
Widths for full-width images. A width in neither size list is refused.
imageSizesnumber[]default [32, 48, 64, 96, 128, 256, 384]
Widths for smaller, fixed-size images such as avatars and icons.
formats("image/webp" | "image/avif")[]default ["image/webp"]
Output formats in order of preference. The first one the browser accepts wins.
qualitiesnumber[]default [75]
Allowed quality values. A request for any other quality is refused.
minimumCacheTTLnumberdefault 14400
Minimum cache lifetime in seconds, even when the source asks for less.
dangerouslyAllowSVGbooleandefault false
Serve SVG sources. Off by default because an SVG can carry script.
maximumRedirectsnumberdefault 3
Redirects followed while fetching a remote source.
fetchTimeoutMsnumberdefault 7000
Timeout for fetching a remote source, in milliseconds.
maxRemoteBytesnumberdefault 26214400 (25 MB)
The largest remote source it downloads.
maxConcurrencynumberdefault 0
Images encoded at once.
0 uses half the CPUs of the serving machine, at least one.- A list replaces its default. Setting
formatsorremotePatternsreplaces that list; lists you leave out keep their defaults. - Remote images are closed by default.
remotePatternsstarts empty, so list every CDN the app shows images from. - AVIF needs an OS codec.
image/avifis encoded only on macOS and Windows; on Linux the optimizer drops it and servesimage/webp.
i18n
i18n lists the languages the app serves. Every route sits under a locale segment such as /en/…:apps/global/akan.config.ts
localesstring[]default ["en", "ko"]
Locale segments the app serves. Each one prefixes every route.
defaultLocalestringdefault "en"
The fallback when none of the browser's languages match. Must be one of
locales.- A bare path redirects. A URL without a locale goes to the best match for the browser's
Accept-Language, or todefaultLocale. - Only availability lives here. Translated copy stays in the dictionary or page that owns the text.


defaultLocale must be one of locales. Setting locales: ["ko", "ja"] alone leaves the default at en, which is no longer listed, so move defaultLocale with it.publicEnv
publicEnv is the allowlist of extra environment variable names that browser code may read. Only the names live here; the values stay in the environment:apps/landing/akan.config.ts
AKAN_PUBLIC_*is always public. Every variable with that prefix is inlined into browser bundles without being listed.- The current build reads only that prefix. Names listed here are not inlined yet, so give a browser-visible variable the
AKAN_PUBLIC_prefix.


Never list a secret. Database URLs, private tokens and server credentials must never reach browser code.
secrets
secrets lists private files that cannot live inside env.server.*.ts, such as service-account JSON, certificates and key files. They travel with the env files and stay out of git:apps/api/akan.config.ts
- Globs relative to the app.
secrets/**/*means every file underapps/<app>/secrets/. - Shipped with the env files.
akan upload-envarchives every match together with the defaultenv/env.client.*.tsandenv/env.server.*.tsfiles, andakan download-envrestores them to the same paths. - Ignored by git on upload. Each
akan upload-envwrites these patterns into a managed block of the workspace.gitignore.


Only the patterns belong in the config. Never commit the files. The
.gitignore block appears on the first akan upload-env, so check git status before committing a new secret file.syncPageLibs
syncPageLibs lets an app serve routes that a library ships in its own page/ folder. The library keeps the route files; the app only opts in.| Value | Routes the app serves |
|---|---|
| false | The default. No library routes; links from an earlier sync are removed. |
| true | Every library dependency that ships a page/ folder. |
| ["shared"] | Only the libraries listed. |
To serve only the routes of
libs/shared:apps/myapp/akan.config.ts
- Routes keep their own path.
libs/shared/page/login/_index.tsxserves/loginin the app. - Edit the library, not the link. The app sees these routes through a generated, git-ignored folder, so changes go in
libs/<lib>/page. - One path, one route. Two synced routes may not resolve to the same path.
externalLibs
externalLibs keeps a package out of the bundle and installs it as a real dependency of the production build. Native and runtime-sensitive packages need this; plain TypeScript helpers do not:apps/media/akan.config.ts
A library declares the packages its own runtime needs the same way:
libs/report/akan.config.ts
- Merged across the workspace. The app's list comes first, then every library's, without duplicates, so
apps/mediaresolves to["shiki", "puppeteer"]. - Every library counts. Not only the app's dependencies are read, so a library declares its package once and no app repeats it.


List the package in the root
package.json too. The production package.json installs the version pinned there.barrelImports
A barrel is an index file that re-exports many files. When code imports
X from a barrel listed in barrelImports, the build points the import at the file that defines X, so the rest of the barrel is never loaded.Already includedDescription
akanjs/webkitakanjs/commonakanjs/uiakanjs/server
The framework facets.
@apps/<app>/{ui,webkit,common,client,server}
This app's own facets.
@libs/<lib>/{ui,webkit,common,client,server}
The same facets of every library in the workspace.
Add only a barrel outside those facets, such as a design-system package:
apps/admin/akan.config.ts
- Resolved like an import. The build looks the barrel up in the tsconfig
pathsfirst, then innode_modules. - Appended, never replacing. Your entries are added after the defaults above.
optimizeImports
optimizeImports names packages whose barrel the browser build parses only as far as you use it. Importing one icon then loads that icon, not the whole set:apps/dashboard/akan.config.ts
Already included
lucide-reactdate-fnslodash-esramdaantdreact-bootstrapahooks@ant-design/icons@headlessui/react@headlessui-float/react@heroicons/react/20/solid@heroicons/react/24/solid@heroicons/react/24/outline@visx/visx@tremor/reactrxjs@mui/material@mui/icons-materialrechartsreact-use@material-ui/core@material-ui/icons@tabler/icons-reactmui-corereact-icons/*- Merged with the defaults. Your entries are added to the list above.
sideEffects: falseneeds no entry. A package whosepackage.jsondeclares it is optimized automatically.- Keep your own barrels clean. One file per export makes the result easy to predict.
docker
docker shapes the production image that akan build writes. Declare it only when the image needs a system package or a different start command.The generated image installs
ca-certificates and tzdata and nothing else, so ffmpeg, a headless browser or a native toolchain goes into preRuns:apps/worker/akan.config.ts
imagestring | { amd64?, arm64? }default oven/bun:1-slim
The base image. The object form picks one per architecture.
preRuns(string | { amd64?, arm64? })[]default []
Steps run before
bun install --production, so native builds find their tools.postRuns(string | { amd64?, arm64? })[]default []
Steps run after the install, before the app files are copied.
commandstring[]default ["bun", "main.js"]
The container's
CMD.Order of the generated Dockerfile
FROMthe image, thenca-certificates,tzdataand the Asia/Seoul timezone.preRuns: the libraries' steps first, then the app's.- Copy
package.jsonand runbun install --production. postRuns, in the same order.- Copy the app files, set
PORT,NODE_ENV, theAKAN_PUBLIC_*values andAKAN_LOG_TO_FILE=0, thenCMD.
- Start with apt-get update. The base step clears the apt package lists, so each install in
preRunsrefreshes them first. - Per-architecture steps. A
{ amd64, arm64 }entry runs each command only on its own architecture of a multi-arch build.
A whole Dockerfile
When the image must be fully under your control, write the whole Dockerfile as a string and keep the order above:
apps/custom-runtime/akan.config.ts
- Start from a generated one.
akan buildwrites the Dockerfile it would use todist/apps/<app>/Dockerfile, with theENVlines your config produces. - Some lines depend on the config. The generated file adds
AKAN_PUBLIC_BASE_PATHSwhen routes declare basePaths, andAKAN_SSR=false/AKAN_CSR=falsewhenwebturns a surface off.


A string is used exactly as written. Nothing is merged into it, including the
preRuns and postRuns your libraries declare.Library Config Fields
A library's
akan.config.ts takes the same object or function shape as an app's. What it declares is added to the apps in the workspace, so no app repeats a library's needs:libs/report/akan.config.ts
Key
Dependents
Other apps
What a library adds
externalLibs
✓
✓
Appended after the app's own list, without duplicates.
docker.{preRuns,postRuns}
✓
✓
Runs before the app's own steps, unless the app writes
docker as a string.assets.keepFonts
✓
Globs against the library's own
public/ whose fonts survive pruning.plugins
✓
Read by the CLI for runtime packages, native setup and assets.
✓AppliedNot applied
- No image and no command. A library adds steps only; the base image and
CMDstay the app's decision. - A real example.
libs/utilships its mobile features this way:plugins: [pushNotificationPlugin, cameraPlugin, …].