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

source
// 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.

source
// 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/.

source
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:

source
router: {
  transitions: 'fade',
}

Or an object with a default and per-route overrides:

source
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.
source
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.