Date: 2026-05-30
Accepted (with caveats — see Open question)
The Admin Console and Self-Service Portal are non-trivial frontends — sidebar shell, datatable with row drawer, ACL matrix, 3-step wizard with QR code, dark/light theming via CSS variables, two surfaces sharing one component family. Built as a real SPA, not server-rendered HTML.
The product is a single-binary self-hosted backend (see ADR-0001). The frontend ships inside the Quarkus binary as static assets under META-INF/resources/. There is no separate frontend deploy target.
The team's strong preference, stated in the project brief, is: avoid an npm-heavy toolchain. Reasons stated:
node_modules is a known weak link for supply-chain attacks; minimizing the dependency tree is desirable.vite.config.ts, tsconfig.json, ESLint, Prettier, …) as a permanent overhead.But: "no npm at all" and "ship a production SPA" pull in opposite directions. Vue 3 itself can run from a CDN ESM bundle (vue.esm-browser.js) with <script type="importmap"> and zero build step. That works in dev. It also works in production, but with caveats: no tree-shaking, no minification of app code, no asset hashing for cache busting, no .vue SFC compilation.
The constraint, restated honestly: minimize npm surface area; accept a small, optional optimization step at release time.
Use Vue 3 with the Composition API, served as plain ES modules. Production uses a small, optional asset-optimization step — not a full Node toolchain.
Development:
<script type="importmap"> pointing at a self-hosted local copy (not a CDN, per N-01)..js files using defineComponent({ template: \...` })— no.vue` SFCs, no template compiler at runtime.@import in a root app.css that pulls in the design system's colors_and_type.css and components.css verbatim.quarkus dev watches src/main/resources/META-INF/resources/).Production:
package.json for the app; esbuild is run as a versioned binary downloaded once, not via npm) bundles and minifies the JS, hashes filenames, and emits a manifest.src/main/resources/META-INF/resources/assets/ and are served by Quarkus.exec-maven-plugin) runs esbuild during package. CI caches the esbuild binary.The point: one binary tool (esbuild), no node_modules directory, no transitive dependency tree.
Baseline: "Plain Vue + standalone esbuild" (the decision above).
| Criterion (weight) | Plain Vue + esbuild (baseline) | Quarkus Quinoa (full Vite) | Vue CDN only, no build | HTMX + server-rendered |
|---|---|---|---|---|
| Avoids npm dep tree (5) | 0 | -1 | +1 | +1 |
| Fits the design (SPA-shaped) (4) | 0 | +1 | 0 | -1 |
| Iteration speed (4) | 0 | 0 | +1 | -1 |
| Production asset quality (3) | 0 | +1 | -1 | 0 |
.vue SFC support (2) |
0 | +1 | -1 | n/a |
| Long-term maintenance burden (3) | 0 | -1 | +1 | +1 |
| Team familiarity with Vue (3) | 0 | 0 | 0 | -1 |
| Weighted total | 0 | +2 | +1 | −4 |
Honest reading of the matrix:
.vue SFCs, hot module replacement, asset hashing). But the −5 weight on "avoids npm dep tree" matters disproportionately for this project's stated values, so the matrix score is misleading without that framing. Quinoa pulls a full Vite + npm tree at build time. The constraint is the constraint.The baseline wins because it threads the needle: respects the no-npm-tree constraint, ships a respectable production bundle, and stays close to standard Vue.
Positive
node_modules. esbuild is a single binary, versioned in tools/ or downloaded by the Maven plugin..js file → Quarkus Live Reload pushes it → browser sees it. No Vite watcher.Risks created
.vue SFC support means components are defineComponent({ template: \...` })` with template strings. Loses IDE template-language support unless Volar is configured for template literals. Mitigation: consistent Vue-style guide; consider Vite-Vue path in v2 if SFC ergonomics become blocking.vue.esm-browser.js (which includes the compiler). That's slightly slower at first paint and slightly larger than the runtime-only build. Acceptable for this scope, but counts as a known cost.Accepted trade-offs
Open question
OQ-2 in docs/prd.md — confirm the esbuild standalone path actually delivers the asset quality we need before locking in. If first-paint on the Portal is unacceptable, revisit with Quinoa or a vendored Vite setup. This ADR is Accepted with the caveat that a spike validates the build pipeline before the first user-visible release.