Skip to content

Frontend Architecture ​

NodeWarden's Web Vault is an original Vite + Preact application inside the repository. It does not depend on the official Bitwarden Web Vault. It covers vault management, Send, import/export, the backup center, device management, administrator pages, and demo mode.

Stack ​

DependencyPurpose
preactComponent runtime.
@tanstack/react-queryRemote data fetching, caching, and refresh.
wouterLightweight routing.
lucide-preactIcons.
@zip.js/zip.js, fflateImport/export and backup ZIP handling.
qrcode-generatorTOTP QR codes.
tailwindcss, postcss, CSSStyling build.

The build entry is webapp/vite.config.ts. Production output goes to root dist/ and is then served by Worker assets.

PWA and Offline Support ​

NodeWarden's Web Vault is a Progressive Web App with offline capabilities:

Service Worker:

  • Generated by Vite during build (webapp/vite.config.ts)
  • Pre-caches app shell (HTML, CSS, JS)
  • Runtime caches static resources and API responses
  • Network-first strategy with offline fallback

Offline Storage:

  • IndexedDB (lib/vault-cache.ts) - Caches vault data (ciphers, folders, sends)
  • localStorage (lib/offline-auth.ts) - Offline unlock records with encrypted profile keys
  • Cache API - Service Worker managed resource cache

Web Worker:

  • Background vault decryption (workers/vault-decrypt.worker.ts)
  • Non-blocking key derivation and cipher decryption
  • Preserves UI responsiveness during large vault operations

Network Detection:

  • Active probing (lib/network-status.ts) with 3.5s timeout
  • Automatic offline/online state management
  • 5-second result caching to avoid excessive requests

PWA Manifest:

  • Installable app with shortcuts (public/manifest.webmanifest)
  • Standalone display mode
  • Quick actions: Open Vault, TOTP Codes

See PWA Features and Offline Usage for user documentation.

App control layer ​

webapp/src/App.tsx is the frontend control layer. It owns session state, login/register, lock/unlock, global queries, notification connection, administrator capability, backup capability, and main route dispatch.

After the main UI becomes usable, app-preload.ts warms some lazy resources and locale chunks in the background to reduce later page-switch latency.

Web Vault login uses X-NodeWarden-Web-Session: 1. The server puts the refresh token into an HttpOnly cookie. The frontend stores only email, auth mode, and unlock session state, avoiding plaintext refresh tokens in localStorage.

Page layers ​

AreaMain components
AuthenticationAuthViews.tsx
App shellAppAuthenticatedShell.tsx, AppMainRoutes.tsx
VaultVaultPage.tsx, components/vault/*
SendSendsPage.tsx, PublicSendPage.tsx
Settings and securitySettingsPage.tsx, SecurityDevicesPage.tsx, PasswordSecurityPage.tsx (route /security/password-health; see Password Security Check)
Administration and backupAdminPage.tsx, BackupCenterPage.tsx, components/backup-center/*
Other toolsDomainRulesPage.tsx, ImportPage.tsx, NotFoundPage.tsx

Large pages should keep using feature-domain subcomponents. The goal is to reduce cognitive load and render cost, not to create tiny wrapper components for their own sake.

Data and API flow ​

Components should prefer helpers under webapp/src/lib/api/ instead of scattered handwritten fetch paths.

Typical flow:

text
component event -> action/hook -> lib/api/* -> authedFetch/session refresh
  -> Worker API -> React Query invalidate/refetch -> UI update

Session logic is mostly around app-auth.ts, app-support.ts, and crypto.ts. Vault decryption logic is mostly in vault-decrypt.ts, decrypt-cipher.ts, vault-worker.ts, and vault-cache.ts.

Routes and permissions ​

The frontend supports regular paths and some hash-route compatibility. Main routes include:

RoutePage
/login, /register, /lock, /recover-2faLogin, registration, lock, and 2FA recovery.
/vault, /vault/totpVault and authenticator code view.
/sendsSend management.
/send/{id}Public Send access, optionally with key.
/settings, /settings/account, /settings/domain-rulesSettings home, account settings, and domain rules.
/security/devicesDevice management.
/backupBackup center, administrator only.
/adminUsers and invites, administrator only.
/backup/import-export and old aliasesImport/export tools.

When adding a page, check:

  • Whether it is accessible before login.
  • Whether it requires admin permission after login.
  • Mobile title, navigation, and back path.
  • Unknown path behavior through NotFoundPage.tsx.
  • Public Send route conflicts with authenticated routes.

Mobile /settings is the settings entry page. Desktop tends to go directly to account settings. Verify both viewport types.

i18n ​

webapp/src/lib/i18n.ts is the language loading entry. Locale files must be complete independent bundles, not incremental overlays on English.

Rules:

  • Persist the selected language locally.
  • Local selection wins over browser language.
  • Load language packs on demand and hard-fallback to English on failure.
  • Add new copy to every locale and run npm run i18n:validate.
  • Do not call t() at module top level to build constants before async initialization.

Production builds split non-English locale packs into separate chunks. Adding a language requires updating the Locale type, available locale list, browser detection, dynamic loader, validation script, and complete locale file.

Build strategy ​

webapp/vite.config.ts has two important strategies:

  • Normal production builds write noindex robots; demo builds allow indexing.
  • Main app pages are grouped into the app-suite chunk, while non-English locales are separate chunks, avoiding a first-screen load of every language and every large page.

Styling and mobile ​

This frontend should feel like serious management software, not a marketing page. When maintaining it:

  • Compress mobile form rhythm as a whole, not just one button.
  • Avoid excessive empty space on settings, backup center, and detail pages on phones.
  • Put secondary explanations behind disclosure or on-demand UI when appropriate.
  • Check dark theme, error states, empty states, and loading states together.

Change rules ​

  • For new remote data, use React Query query/mutation paths and shared error handling.
  • For new routes, check App.tsx, navigation, 404, and mobile titles.
  • For large features, consider lazy loading or background preloading.
  • For cipher shape changes, check encryption, decryption, import, export, sync, and official-client compatibility.