DEV Community

Cover image for Why We Chose 100% Client-Side Computation for Engineering Calculators (Zero Database, Zero Tracking)
miad
miad

Posted on Originally published at hvaclogic.org AI-assisted

Why We Chose 100% Client-Side Computation for Engineering Calculators (Zero Database, Zero Tracking)

Modern web architecture has fallen into an unnecessary trap: wrapping every deterministic formula in a serverless microservice, routing basic arithmetic through cloud databases, and requiring user sign-ups just to solve a physical equation.

When building HVACLogic, an open engineering suite for mechanical designers, field technicians, and building scientists, we made a conscious architectural departure from conventional SaaS models:

100% client-side computation. Zero backend database. Zero calculation telemetry. Zero user authentication.

Here is why we eliminated the server from our engineering stack, how we solved URL state synchronization without a persistence layer, and how pure TypeScript functions outperform cloud APIs on mechanical job sites.


1. The Physical Case for Zero-Server Engineering

Mechanical sizing calculations are governed by physical conservation laws. Whether you are solving thermal envelope heat transfer:

q = U * A * delta_T
Enter fullscreen mode Exit fullscreen mode

Or evaluating sensible cooling capacity from supply airflow:

q_sensible = 1.08 * CFM * delta_T
Enter fullscreen mode Exit fullscreen mode

The mathematical output is strictly deterministic. Given identical environmental inputs, the result never changes.

Placing a cloud database (Postgres, DynamoDB, Supabase) between an engineer and a deterministic equation creates three major liabilities:

  1. Network Latency on Job Sites: Mechanical contractors work in mechanical penthouses, basements, and remote commercial sites with poor cellular reception. A serverless API call introduces 150ms to 800ms of latency, or fails entirely. A local browser thread computes in under 0.5 milliseconds.
  2. Data Privacy and Trade Secrets: Equipment bids, room dimensions, building envelope heat loss values, and customer addresses are sensitive project data. Routing project numbers and structural dimensions through a third-party server creates an unneeded data liability.
  3. Database Maintenance and Infrastructure Fragility: Databases crash, connection pools exhaust, schema migrations break, and hosted tiers cost money. A static, client-evaluated web app has near-zero operational overhead and runs indefinitely.

2. The Core Architecture: Pure Functional Physics

Every calculation module in our suite is structured as a pure TypeScript mathematical function with zero external side effects and zero DOM coupling.

Consider our envelope cooling and heating load engine used in the ACCA Manual J Cooling Load & BTU Calculator:

export interface HeatLoadInputs {
  squareFootage: number;
  ceilingHeight: number;
  occupants: number;
  insulationLevel: 'poor' | 'average' | 'good' | 'exceptional';
  windowExposure: 'north' | 'south' | 'east' | 'west';
  climateZone: number;
  designDeltaT: number;
}

export interface HeatLoadOutputs {
  sensibleCoolingBtu: number;
  latentCoolingBtu: number;
  totalCoolingBtu: number;
  heatingLoadBtu: number;
  recommendedTonnage: number;
  requiredAirflowCfm: number;
}

export function computeManualJLoad(inputs: HeatLoadInputs): HeatLoadOutputs {
  const envelopeVolume = inputs.squareFootage * inputs.ceilingHeight;

  // Base envelope heat transfer coefficients (U-effective)
  const uFactors = {
    poor: 0.12,
    average: 0.08,
    good: 0.05,
    exceptional: 0.035,
  };

  const uEffective = uFactors[inputs.insulationLevel];
  const envelopeArea = inputs.squareFootage * 1.5; // Empirical envelope ratio

  // Sensible conduction load
  const conductionBtu = uEffective * envelopeArea * inputs.designDeltaT;

  // Internal occupant gains (ASHRAE Standard 55: 230 Btu/h sensible, 200 Btu/h latent)
  const occupantSensible = inputs.occupants * 230;
  const occupantLatent = inputs.occupants * 200;

  // Infiltration load (Sherman-Grimsrud air exchange approximation)
  const achNatural = inputs.insulationLevel === 'poor' ? 0.8 : 0.35;
  const infiltrationCfm = (envelopeVolume * achNatural) / 60;
  const infiltrationSensible = 1.08 * infiltrationCfm * inputs.designDeltaT;

  const totalSensible = conductionBtu + occupantSensible + infiltrationSensible;
  const totalCooling = totalSensible + occupantLatent;
  const tonnage = totalCooling / 12000;
  const airflow = totalSensible / (1.08 * 20); // 20 deg F evaporator split

  return {
    sensibleCoolingBtu: Math.round(totalSensible),
    latentCoolingBtu: Math.round(occupantLatent),
    totalCoolingBtu: Math.round(totalCooling),
    heatingLoadBtu: Math.round(conductionBtu * 1.25 + infiltrationSensible * 1.1),
    recommendedTonnage: Number(tonnage.toFixed(2)),
    requiredAirflowCfm: Math.round(airflow),
  };
}
Enter fullscreen mode Exit fullscreen mode

Because this calculation is a pure function, testing is straightforward. We maintain 100% test coverage using Vitest, executing hundreds of unit test assertions in under 200 milliseconds.


3. State Management Without a Database: URL Serialization

If you do not have a database, how do users save, share, and bookmark complex multi-parameter calculations?

We utilize bi-directional URL search parameter hydration. The entire calculation state is encoded directly into the browser URL:

https://hvaclogic.org/calculators/btu-calculator?sqft=2400&height=9&occupants=4&insulation=good&deltaT=30
Enter fullscreen mode Exit fullscreen mode

The URL Sync Hook

We synchronize component state with the URL bar using a lightweight custom hook:

import { useSearchParams, useRouter, usePathname } from 'next/navigation';
import { useCallback } from 'react';

export function useCalculationParams<T extends Record<string, any>>(defaultValues: T) {
  const router = useRouter();
  const pathname = usePathname();
  const searchParams = useSearchParams();

  // Read state from URL or fallback to defaults
  const getParamState = useCallback((): T => {
    const state = { ...defaultValues };
    for (const key of Object.keys(defaultValues)) {
      const val = searchParams.get(key);
      if (val !== null) {
        state[key as keyof T] = typeof defaultValues[key] === 'number' 
          ? (Number(val) as any) 
          : (val as any);
      }
    }
    return state;
  }, [searchParams, defaultValues]);

  // Push state update to URL without triggering a full page re-render
  const setParamState = useCallback((newValues: Partial<T>) => {
    const params = new URLSearchParams(searchParams.toString());
    Object.entries(newValues).forEach(([key, val]) => {
      params.set(key, String(val));
    });
    router.replace(`${pathname}?${params.toString()}`, { scroll: false });
  }, [searchParams, pathname, router]);

  return { getParamState, setParamState };
}
Enter fullscreen mode Exit fullscreen mode

The Architectural Benefits of URL-as-State:

  • Zero Storage Costs: No database rows, NoSQL collections, or session caches.
  • Infinite Permalinks: An engineer can email or text a calculation link to a field tech, code inspector, or client. The recipient opens the exact calculation instantly, pre-hydrated.
  • Full History Support: The browser native Back and Forward buttons navigate across calculation iterations for free.

4. Real-Time Interactivity: Reactive SVG Rendering

Instead of relying on heavy third-party canvas or charting libraries (Chart.js, D3) that bloat bundle sizes by hundreds of kilobytes, we render mechanical diagrams using native, reactive inline SVG.

When a technician adjusts duct dimensions or airflow CFM in our Airflow CFM & Sensible Heat Calculator, the SVG geometry updates at 60 FPS directly via React state:

export function DuctCrossSectionVisualizer({ width, height, aspect }: { width: number; height: number; aspect: number }) {
  const scale = 180 / Math.max(width, height, 1);
  const rectW = width * scale;
  const rectH = height * scale;
  const isHighAspect = aspect > 4.0;

  return (
    <svg viewBox="0 0 240 240" className="w-full h-48 rounded-lg bg-slate-900 border border-slate-800">
      <rect
        x={(240 - rectW) / 2}
        y={(240 - rectH) / 2}
        width={rectW}
        height={rectH}
        fill="rgba(14, 165, 233, 0.15)"
        stroke={isHighAspect ? '#f43f5e' : '#0ea5e9'}
        strokeWidth="2"
        rx="4"
      />
      <text x="120" y="230" textAnchor="middle" fill="#94a3b8" className="text-xs font-mono">
        {width}" x {height}" (Aspect Ratio: {aspect.toFixed(1)}:1)
      </text>
    </svg>
  );
}
Enter fullscreen mode Exit fullscreen mode

This delivers instant visual feedback on fluid flow boundaries, aspect ratio penalties, and coil velocity without loading any external dependencies.


5. Performance Comparison: Client vs. Cloud API

Here is how our client-side architecture compares to a typical server-backed web application:

Performance Metric Traditional Serverless API HVACLogic Client-Side Architecture
First Calculation Latency 350ms - 900ms (Cold start + TLS + DB) 0.2ms - 0.5ms (In-memory V8 runtime)
Subsequent Parameter Tweak 120ms - 250ms (Network round-trip) Instant (< 16ms / 60 FPS)
Offline Job-Site Usability Fails completely (Network error) 100% Functional via Service Worker
User Privacy & PII Exposure High (Data stored on server tables) Zero (Data never leaves browser)
Monthly Database Hosting Cost Scales with active monthly queries $0.00 (Static edge delivery)

Summary and Architecture Takeaways

Building deterministic engineering software on the web does not require microservices, database schemas, or account walls.

By structuring calculation logic into pure TypeScript functions, using the URL query string as a state persistence layer, and pairing pure math with reactive SVG visualizers, you can create blisteringly fast, privacy-safe, and offline-capable tools that serve engineers anywhere.

To explore the open mathematical models and test the calculation engines yourself, check out the HVACLogic Deterministic Engineering Architecture and our open tools across the platform.

Top comments (0)