Home Documentation Templates Examples Showcase GitHub
Theme

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

ConcernOwner
Document shell, navigation, metadata, initial accessible markupNift templates and content
Browser bundle, event handling, local UI stateVanilla JavaScript or React/Vue/Svelte
Authentication, authorization, validation and business rulesBackend
Live records, events and persistenceBackend/database
TLS, headers, caching and request routingWeb 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.
Nift builds the dashboard; it is not the dashboard server.

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.