TypeScript

TypeScript for Industrial Software: Migrating Legacy Systems

By Technspire Team
April 11, 2026
4 views

Industrial software written in Java, C#, VB.NET, or Delphi accumulates for decades in manufacturing, automotive, and energy organisations. The underlying backend logic often works fine; the operator interfaces and customer-facing web surfaces are where pressure for modernisation lands. TypeScript, paired with Next.js or a modern SPA framework, has become the typical modernisation target. This walk-through covers the migration patterns that actually work, the cases where a full rewrite is the wrong answer, and the specific decisions that determine whether the modernisation ships.

When TypeScript Wins

  • Web-first user experience. Browser and mobile web surfaces are the dominant delivery channel for operators, field service technicians, and partners. TypeScript + modern React or similar stacks deliver here with an ecosystem that native desktop frameworks cannot match.
  • Shared types across the boundary. Server-rendered Next.js applications, Server Actions, and monorepos allow the same TypeScript type to describe both backend and frontend contracts. Java-to-JavaScript boundaries lose all type safety; TypeScript-to-TypeScript preserves it.
  • Developer pool. Hiring TypeScript and React engineers in the European market, including industrial hubs like the Ruhr, Stockholm, or Milan, is easier in 2026 than hiring Java Swing or WPF engineers. Team composition matters for long-term maintainability.
  • Toolchain maturity. TypeScript's type system now handles complex industrial domain models with branded types, discriminated unions, and template literal types that older TypeScript releases could not. Zod and io-ts provide runtime validation that bridges the network boundary.

When TypeScript Is the Wrong Answer

  • Hard real-time or deterministic-timing code. PLC logic, machine controllers, and safety-critical interlocks belong in domain languages (IEC 61131-3, C, Ada). Not TypeScript.
  • Heavy numerical or signal-processing cores. MATLAB/Simulink-generated code, digital signal processing, and large matrix computations are better served by Julia, C++, or Rust with TypeScript bindings only at the integration layer.
  • Existing .NET backend with no web surface change planned. A working C# backend that serves a legacy Windows Forms frontend is not rescued by TypeScript. Migrating the frontend to TypeScript + Next.js is a productive move; ripping out the C# backend to rewrite in Node is usually not.

The Migration Patterns

Pattern 1: Strangler fig on the frontend, keep the backend

The common and usually correct pattern. The existing Java or C# backend stays in place, exposing REST or GraphQL APIs. A new TypeScript frontend (Next.js, React with Vite, or similar) is built alongside, taking over user-facing routes one by one. The old frontend is decommissioned per-route once the new one covers it.

This pattern carries the least risk because the business logic, database integrations, and backend-to-backend integrations remain untouched. The migration is scoped to the presentation layer, which is where user value flows anyway.

Pattern 2: BFF (Backend-for-Frontend) in TypeScript

A TypeScript Node.js or Next.js Server Actions layer sits between the legacy backend and the browser. It translates legacy API shapes into frontend-friendly contracts, applies caching, and handles authentication. The BFF pattern decouples frontend evolution from backend evolution — the frontend team can iterate quickly while the legacy backend remains stable.

// Next.js Server Action calling a legacy Java backend
'use server';

import { z } from 'zod';

const WorkOrderInput = z.object({
  orderId: z.string().regex(/^WO-\d{6}$/),
  action: z.enum(['start', 'pause', 'complete']),
});

export async function updateWorkOrder(raw: unknown) {
  const input = WorkOrderInput.parse(raw);

  const res = await fetch(`${process.env.LEGACY_MES_URL}/workorders/${input.orderId}`, {
    method: 'PATCH',
    headers: {
      'Content-Type': 'application/json',
      Authorization: await getServiceToken(),
    },
    body: JSON.stringify({ state: input.action }),
  });

  if (!res.ok) throw new Error(`MES ${res.status}: ${await res.text()}`);
  return res.json();
}

Pattern 3: Full backend migration (rarely correct)

Replacing a working C# or Java backend with Node.js is rarely the right call. It is a 12–36 month project that delivers roughly the same functionality in a different runtime. Exceptions exist: when the legacy codebase has become unmaintainable, when hiring in the legacy language is impossible, or when the database migration is needed anyway. Most shops overestimate the benefit and underestimate the cost of this path.

Runtime Validation at the Boundary

TypeScript's type system disappears at runtime. When the frontend receives data from a legacy backend, the types on the TypeScript side are aspirational — the actual data can be anything. Runtime validation with Zod, io-ts, or similar closes the gap:

// Runtime validation of legacy API response
import { z } from 'zod';

const LegacyMachineStatus = z.object({
  machineId: z.string(),
  state: z.enum(['RUNNING', 'STOPPED', 'FAULT', 'MAINTENANCE']),
  uptime_ms: z.number().int().nonnegative(),
  currentOrder: z.string().nullable(),
  alarms: z.array(z.object({
    code: z.string(),
    severity: z.enum(['info', 'warning', 'critical']),
    raisedAt: z.string().datetime(),
  })),
});

export async function getMachineStatus(machineId: string) {
  const raw = await fetch(`${LEGACY_BASE}/machines/${machineId}`).then(r => r.json());
  return LegacyMachineStatus.parse(raw);          // throws on shape mismatch
}

Shared Types for Monorepos

For new greenfield components where both the API and the frontend are new, a TypeScript monorepo (pnpm workspaces, Turborepo, Nx) with a shared types package prevents drift. A contract change shows up as a compile error on both sides simultaneously.

Identity, Authentication, and Authorization

Industrial organisations typically centralise identity in Azure Entra (formerly Azure AD), Okta, or Keycloak. A TypeScript frontend integrates via OAuth 2.0 / OpenID Connect. For Azure Entra environments, NextAuth.js or Auth.js paired with the Entra provider is the default stable path. Role-based access control uses Entra group memberships as the authorisation source, the frontend applies role-scoped UI visibility, and the backend (legacy or new) enforces actual authorisation on every request.

Localisation for European Industrial Markets

Operator interfaces in European manufacturing need at minimum German, French, Italian, Spanish, and Swedish. Next.js with next-intl provides the right primitives: file-based message catalogues per locale, middleware-driven locale routing, right-to-left support where needed, and per-locale SEO metadata. The translation workflow typically pipes through a CAT tool (e.g. memoQ, Smartling) rather than hand-maintaining catalogues.

Accessibility and Industrial UX

Industrial operator interfaces have unusual accessibility profiles: users wear gloves, view screens in bright or dim light, and may have hand-eye coordination constrained by PPE. WCAG 2.1 AA is the compliance baseline. Beyond that, industrial UI design means larger touch targets (44×44 px minimum, ideally 56×56), high-contrast colour choices, and clear disabled/enabled states. TypeScript and React component libraries (MUI, Radix, Chakra) provide the accessibility hooks; the design system has to apply them thoughtfully for the industrial context.

The Migration Timeline, Honestly

  • Small frontend (one team, 5–10 screens): 3–6 months to feature parity with the legacy UI.
  • Medium frontend (two teams, 30–60 screens, multiple modules): 9–18 months.
  • Large frontend (plant-wide operator portal, 100+ screens, real-time data): 18–36 months, typically delivered in vertical slices.

These timelines assume the backend stays in place. Full rewrites double or triple the numbers. The frontend scope alone is usually the right commitment.

The Decision Framework

  1. Is the existing backend stable, maintainable, and meeting requirements? If yes, leave it. Migrate only the frontend.
  2. Is the user experience the bottleneck? If yes, TypeScript + modern web framework on the frontend is the right lever.
  3. Is real-time or deterministic-timing code involved? If yes, keep that in its existing runtime; integrate via bounded contracts.
  4. Is hiring in the legacy language a sustained problem? If yes, the migration argument strengthens; but plan for a multi-year journey.
  5. Is the migration sponsored by a clear user-value case (faster iterations, new features, measurable UX improvement)? If no, do not start.

TypeScript migrations of industrial software succeed when scoped to the frontend, when the backend stays productive in its current form, and when the migration is driven by user-value rather than resume-building. Succeed on those three conditions and the resulting system is quicker to iterate on, easier to hire for, and more accessible to modern ecosystem tools. Miss them and the migration becomes a re-platforming expense that ships nothing new for two years.

Ready to Transform Your Business?

Let's discuss how we can help you implement these solutions and achieve your goals with AI, cloud, and modern development practices.

No commitment required • Expert guidance • Tailored solutions