Configuration
JAF configuration lives in config/jaf.config.js. The file is optional -
every option has a default, and a scaffolded project runs without touching it.
It is read as plain ESM by both the Vite plugins at build time and the router at runtime.
The path is hardcoded: config/jaf.config.js, in the project root, with a
.js extension. A jaf.config.ts, or the same file at the top
level, is not picked up.
Shape
// config/jaf.config.js
export default {
componentsPath: '/pages/components',
scanPaths: ['pages'],
router: {
root: '#app',
fileRouting: true,
prefetch: true,
transitions: 'fade',
},
routing: {
pagesDir: 'pages',
layoutFile: 'layout.html',
errorsDir: 'errors',
ignore: ['components/**', 'partials/**', 'shared/**'],
},
guards: {
redirect: '/login',
},
runtimeCompile: false,
leanRuntime: 'auto',
query: {
adapter: 'basic',
},
namespaces: {
libraries: { flowbite: 'lib/flowbite' },
warnOnConflicts: true,
},
}
Component Resolution
| Option | Type | Default | Description |
|---|---|---|---|
componentsPath |
string |
'/pages/components' |
Directory prefix tried first when resolving a component by name at runtime. |
scanPaths |
string[] |
project root | Directories the build scans for .html components. Narrowing this speeds up large projects and avoids scanning unrelated folders. |
scanPaths can also be passed inline to the Vite plugin.
The inline value wins; jaf.config.js is only consulted when the plugin was
given none.
// vite.config.ts
jafComponentsPlugin({ scanPaths: ['pages', 'lib'] })
Router
| Option | Type | Default | Description |
|---|---|---|---|
router.root |
string | HTMLElement |
'#app' |
Where the router mounts pages. |
router.fileRouting |
boolean |
true |
Load the generated route manifest. Set false to supply routes yourself. |
router.prefetch |
boolean |
true |
Prefetch all route chunks after the first page renders. |
router.autoInit |
boolean |
true |
Dev only. Set false to stop the dev server injecting entry-client.ts into the shell, when you bootstrap it yourself. |
router.transitions |
see below | off | View Transitions between routes. |
router.mode |
'history' | 'hash' |
'history' |
URL strategy. |
router.basePath |
string |
'' |
Prefix stripped from and added to every route path, for apps served under a subdirectory. |
Every router option works here. The whole
router object is forwarded to createRouter().
Annotate your config with
/** @type {import('jafjs').JafConfig} */ for completion on
all of it.
router.basePath
Set this when the app is served from a subdirectory rather than the root of a domain -
https://example.com/app/ instead of https://example.com/.
router: {
basePath: '/app',
}
Your route table stays exactly as it is.
The prefix is stripped from every URL arriving from outside - a link
href, a navigate() call, the first load, a back or
forward - and added back to every URL the router writes.
Everything in between is prefix-free: route.path is
'/about', so route names, guards, active links and
generatePath() keep working untouched.
The strip is tolerant, so both spellings of a link reach the same route and
both end up in the address bar as /app/about.
The prefix has to end on a segment boundary, so /application/about
is a different place and 404s as it should.
'/app', 'app', '/app/' and
'app/' are one configuration; '' and
'/' both mean "served at the root".
In mode: 'hash' the prefix belongs to the document's own path and
never appears after the #, so basePath does nothing
there - serve the app from the subdirectory and hash routing works unchanged.
Also set your bundler's base. basePath only
governs router URLs. Asset URLs are Vite's job - set
base: '/app/' in vite.config.ts so scripts,
styles and images resolve too.
router.transitions
Either a single value applied to every navigation:
router: {
transitions: 'fade',
}
Or an object with a default and per-route overrides:
router: {
transitions: {
default: 'fade',
routes: {
'/gallery/*': 'zoom',
'/docs/**': 'none',
},
},
}
| Value | Meaning |
|---|---|
'fade' |
Built-in cross-fade, 200ms |
'slide' |
Built-in horizontal slide, 260ms, reversed on back navigation |
'zoom' |
Built-in scale, 220ms |
true |
Same as 'fade' |
'none', false, '' |
Disabled |
| any other string | A custom name. JAF ships no CSS for it; you write the ::view-transition-* rules. |
Omitting transitions entirely disables them. Supplying routes
without a default makes the default 'fade'.
The most specific matching route pattern wins over default, and a
per-navigation option beats both. See
Route transitions for the full surface.
Routing
| Option | Type | Default | Description |
|---|---|---|---|
routing.pagesDir |
string |
'pages' |
Directory scanned to build routes. |
routing.layoutFile |
string |
'layout.html' |
Filename treated as a layout rather than a page. |
routing.errorsDir |
string |
'errors' |
Directory inside pagesDir holding error pages. See Error Pages. |
routing.ignore |
string[] |
['components/**', 'partials/**', 'shared/**'] |
Globs inside pagesDir that never become routes. |
These can also live in config/routing.js, which is merged into the same
routing namespace.
Use whichever you prefer, not both.
Guards
| Option | Type | Default | Description |
|---|---|---|---|
guards.redirect |
string |
'/' |
Where a denied <guard> navigates when the tag carried no
redirect=. Denial is always a navigation, so a project with a login page
names it once here rather than on every guard. See
Route Guards.
|
The allow list itself is a different file - config/guards.config.js. This is
the one guard decision the build makes.
Compilation
| Option | Type | Default | Description |
|---|---|---|---|
runtimeCompile |
boolean |
false |
Keep the string-eval script path in production builds. Production script execution is
otherwise a code-table lookup that ships no new Function; opting in re-adds
the runtime interpreter and the CSP 'unsafe-eval' requirement with it.
Compiling source a visitor typed does not need it - the browser can run the compiler
and emit the same module a build does. See
Security.
|
leanRuntime |
boolean | 'auto' |
'auto' |
Capability dropping for production builds. 'auto' drops what the compiled
payloads prove unreachable, false ships the full runtime,
true forces it on. An app whose users can reach any API at runtime - a REPL
again - sets false, because "nothing can reach this" cannot be proved
there.
|
runtimeCompile is not a way to silence a build error. A shape the compiler
cannot express stops the build naming the component and the reason, and fixing the shape
is the fix.
Query
| Option | Type | Default | Description |
|---|---|---|---|
query.adapter |
'basic' | 'tanstack' |
'basic' |
Backing cache for query() and mutation(). 'tanstack' requires @tanstack/query-core to be installed. |
See Adapters for the behavioural differences.
Namespaces
| Option | Type | Default | Description |
|---|---|---|---|
namespaces.libraries |
Record<string, string> |
none | Explicit namespace name to folder mapping, e.g. { flowbite: 'lib/flowbite' }. |
namespaces.paths |
string[] |
none | Extra directories to scan for namespaced component libraries. Appended to scanPaths. |
namespaces.warnOnConflicts |
boolean |
true |
Log a build warning when two files claim the same component name. |
See Component Namespaces.
Multiple Apps
| Option | Type | Default | Description |
|---|---|---|---|
apps |
Record<string, string> |
{} |
URL prefix to entry HTML file. Used by the dev server's SPA fallback so several shells can share one project. The longest matching prefix wins. |
apps: {
'/admin': 'admin.html',
'/': 'index.html',
}
Build Configuration
Chunking and build tuning live in a separate file, config/build.js, because
they are consumed by vite.config.ts rather than by JAF itself:
| Option | Type | Description |
|---|---|---|
vendors |
Record<string, string[]> |
Chunk name to npm package names, for manual vendor splitting. |
routeGroups |
Record<string, string[]> |
Group routes into shared page chunks. |
sharedComponentsChunk |
boolean |
Emit one chunk for shared components. Default true. |
excludePages |
string[] |
Globs excluded from routing in production builds only. Merged with routing.ignore. |
dropConsole |
boolean |
Strip console and debugger in production. Default true. |
Vite Plugins
JAF is a set of Vite plugins.
Most take no options; the ones that do are configured inline in
vite.config.ts, not in jaf.config.js:
| Plugin | Options |
|---|---|
jafComponentsPlugin |
compile (default true), cache (default true), scanPaths |
jafPlugin, fileRouterPlugin, jafExpressionPlugin, pageAssetsPlugin |
none |
componentTypesCodegenPlugin |
output, pagesDir, componentsDir, verbose |
namespaceCodegenPlugin |
namespace codegen output options |
See TypeScript for what the codegen plugins emit.