Website Frontend Development with TypeScript

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.
Development and maintenance of all types of websites:
Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:
Development stages
Latest works
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    822
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    847
  • image_website-sbh_0.png
    Website development for SBH Partners
    999
  • image_website-_0.png
    Website development for Red Pear
    451

TypeScript Frontend Website Development

TypeScript is not just "JavaScript with types." It's a tool that makes refactoring predictable, documents contracts between modules, and catches an entire class of errors at compile time. For projects with team of two or lifetime longer than a year, TypeScript is not an option, it's a basic requirement.

We use TypeScript as primary language for all frontend: from Vite configuration to API response typing.

Configuration for strict TypeScript

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,
    "exactOptionalPropertyTypes": true,
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

strict: true enables 8 flags at once. noUncheckedIndexedAccess adds undefined to type when accessing array by index — half of runtime errors disappear by themselves.

API typing

API response types — one of the first steps on any project. Manual writing is tedious and becomes outdated with backend. Right approach:

OpenAPI → auto-generate types:

npx openapi-typescript https://api.example.com/openapi.json -o src/types/api.ts

Result — precise types for all endpoints. Then type-safe client:

import createClient from 'openapi-fetch';
import type { paths } from '@/types/api';

const client = createClient<paths>({ baseUrl: 'https://api.example.com' });

// TypeScript knows parameter and response types
const { data, error } = await client.GET('/products/{id}', {
  params: { path: { id: '42' } }
});

if (data) {
  console.log(data.name); // string — without as, without any
}

Patterns for scalable frontend

Discriminated union for loading states:

type AsyncState<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; message: string };

function renderState<T>(state: AsyncState<T>): string {
  switch (state.status) {
    case 'idle': return 'Click to load';
    case 'loading': return 'Loading...';
    case 'success': return `Loaded: ${JSON.stringify(state.data)}`;
    case 'error': return `Error: ${state.message}`;
  }
}

Compiler checks exhaustiveness — if add new status, all switch expressions without it become error.

Branded types to prevent ID confusion:

type UserId = string & { readonly __brand: 'UserId' };
type ProductId = string & { readonly __brand: 'ProductId' };

function toUserId(id: string): UserId { return id as UserId; }

function getUser(id: UserId): Promise<User> { ... }

const productId: ProductId = toProductId('abc');
getUser(productId); // Compile error: ProductId not compatible with UserId

Zod for runtime data validation:

import { z } from 'zod';

const ProductSchema = z.object({
  id: z.string().uuid(),
  name: z.string().min(1).max(255),
  price: z.number().positive(),
  category: z.enum(['electronics', 'clothing', 'books']),
  tags: z.array(z.string()).default([]),
});

type Product = z.infer<typeof ProductSchema>; // Type inferred automatically

// API response validation
const raw = await fetch('/api/products/42').then(r => r.json());
const product = ProductSchema.parse(raw); // Throws if invalid

Linting setup

// .eslintrc or eslint.config.js
{
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/strict-type-checked",
    "plugin:@typescript-eslint/stylistic-type-checked"
  ],
  "rules": {
    "@typescript-eslint/no-explicit-any": "error",
    "@typescript-eslint/consistent-type-imports": "error",
    "@typescript-eslint/no-floating-promises": "error"
  }
}

no-floating-promises catches incomplete promise chains — entire class of errors that otherwise silently get swallowed.

Framework integration

TypeScript works with any frontend stack:

Framework Support level
React 18 Full, JSX via tsx
Vue 3 Full, SFC via <script setup lang="ts">
Svelte 4/5 Via lang="ts" in script block
Solid.js First-class support
Vanilla (no framework) Full

Timeline

  • Week 1: tsconfig setup, ESLint, type generation from OpenAPI/GraphQL schemas
  • Weeks 2–3: component and business logic development with full typing
  • Week 4: code review for any, refactoring weakly typed places, tests with Vitest

Project delivered with zero any in production code and strict: true enabled. Exceptions documented with explicit comment.