Pattern
Dashboards.
A dashboard mixes stable document structure with live authenticated state. Nift can own the shell, navigation, checked assets and build-time configuration; browser code and the backend own requests, authorization and changing data.
The ownership boundary
| Concern | Owner |
|---|---|
| Document shell, navigation, metadata, initial accessible markup | Nift templates and content |
| Browser bundle, event handling, local UI state | Vanilla JavaScript or React/Vue/Svelte |
| Authentication, authorization, validation and business rules | Backend |
| Live records, events and persistence | Backend/database |
| TLS, headers, caching and request routing | Web server/deployment platform |
A coherent project shape
content/dashboard/
index.html
events.html
settings.html
templates/
dashboard.html
partials/dashboard-nav.html
frontend/
src/dashboard.js
backend/
main.go # or Node/Python/C++/another runtime
public/
assets/dashboard.js
.nift/ The top-level build script makes the ordering clear:
frontend:
npm --prefix frontend run build
site: frontend
nift build
release: frontend
nift build --all
serve: site
go run ./backend Nift generates the shared shell
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>$[title] · Operations</title>
<link rel="stylesheet" href="@pathto('public/assets/dashboard.css')">
</head>
<body>
@input('templates/partials/dashboard-nav.html')
<main id="main" class="dashboard">@content</main>
<script type="module" src="@pathto('public/assets/dashboard.js')"></script>
</body>
</html> Navigation remains ordinary checked markup:
<nav aria-label="Dashboard">
<a href="@pathto('dashboard/index')">Overview</a>
<a href="@pathto('dashboard/events')">Events</a>
<a href="@pathto('dashboard/settings')">Settings</a>
</nav> Content provides useful initial markup
<h1>System status</h1>
<p id="dashboard-status" role="status" aria-live="polite">
Loading current status…
</p>
<section aria-labelledby="service-health-heading">
<h2 id="service-health-heading">Services</h2>
<div id="service-health">
<p>Live service data is unavailable until the dashboard connects.</p>
</div>
</section>
<section aria-labelledby="recent-events-heading">
<h2 id="recent-events-heading">Recent events</h2>
<ol id="recent-events"></ol>
</section> This is progressive enhancement, not an empty page that exists only after JavaScript succeeds. Loading, empty, error and disconnected states should be designed deliberately.
Browser code loads current state
const status = document.querySelector('#dashboard-status');
const services = document.querySelector('#service-health');
async function loadSnapshot() {
status.textContent = 'Loading current status…';
const response = await fetch('/api/status', {
credentials: 'same-origin',
headers: { Accept: 'application/json' }
});
if (!response.ok) throw new Error(`status request failed: ${response.status}`);
const snapshot = await response.json();
services.replaceChildren(...snapshot.services.map(renderService));
status.textContent = `Updated ${new Date(snapshot.observedAt).toLocaleTimeString()}`;
}
loadSnapshot().catch((error) => {
console.error(error);
status.textContent = 'Current status is unavailable. Retry shortly.';
}); The backend must validate access and return only data the current user may see. Hiding a panel in generated HTML or JavaScript is not authorization.
Realtime is an enhancement
function connectEvents() {
const stream = new EventSource('/api/events/stream');
stream.addEventListener('message', ({ data }) => {
prependEvent(JSON.parse(data));
});
stream.addEventListener('error', () => {
status.textContent = 'Live updates disconnected; showing the last snapshot.';
});
}
connectEvents(); Fetch plus polling may be enough. Server-Sent Events fit one-way event streams; WebSockets fit genuinely bidirectional sessions. Nift's role does not change with that runtime choice.
Build-time configuration versus secrets
Public values such as an API base path or feature label may be emitted into markup or a JSON script block. Secrets must not be placed in Nift source, generated HTML or browser bundles: anything delivered to the browser is public to that user.
<script type="application/json" id="dashboard-config">
{
"apiBase": "/api",
"eventsEnabled": true
}
</script> When a framework earns its place
Replace the browser module with a React, Vue or Svelte entry when the dashboard develops complex shared client state, optimistic mutations, reusable interactive components or substantial client-side routing. Keep the surrounding document in Nift if that separation remains useful; let the application own the whole route when the runtime UI genuinely becomes the whole page.
Security and deployment checklist
- Require authorization in every backend endpoint; never rely on hidden frontend controls.
- Use same-site secure cookies or another deliberately reviewed authentication scheme.
- Protect state-changing requests against CSRF where the authentication model requires it.
- Apply a Content Security Policy and avoid inline secrets or executable configuration.
- Give loading, empty, error, stale and disconnected states accessible text.
- Build browser assets before Nift, then run
nift build --all. - Test the generated shell without JavaScript and the composed application with the real backend.
The clean boundary is the point: Nift makes the stable frontend structure reusable and checked, while runtime code remains responsible for live data and security.