Skip to main content

TypeScript React CI: Type-Check in Your Pipeline

TypeScript adds compile-time type checking to JavaScript, catching type mismatches before code runs. Running the TypeScript compiler (tsc) in your CI pipeline prevents unsafe type violations from reaching production. When a developer passes the wrong type to a function or accesses a non-existent property, the CI build fails and they get a clear error message to fix before merging.

Why Type-Check in CI?​

Type checking is a form of automated testing that finds entire categories of bugs (null pointer exceptions, wrong argument types, missing properties) without writing a single test. Teams using TypeScript report 15-38% fewer production bugs (source: Stack Overflow 2023 Developer Survey). In CI, type checking acts as a safety net: it verifies every commit against your defined types and prevents untyped or incorrectly-typed code from reaching main.

Without CI type-checking, a developer might disable TypeScript locally, check in untyped code, and bypass all type safety. CI enforces that all code conforms to your type definitions.

Configuring TypeScript for React​

Ensure TypeScript is installed in your React project:

npm install --save-dev typescript @types/react @types/react-dom

Create a tsconfig.json in your project root:

{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"noEmitOnError": true,
"resolveJsonModule": true,
"jsx": "react-jsx",
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}

Key settings:

  • strict: true: Enables all strict type checking options (recommended for CI)
  • noEmitOnError: true: Prevents emitting JavaScript if type errors exist
  • jsx: "react-jsx": Enables JSX syntax in .tsx files

Add a type-check script to package.json:

{
"scripts": {
"type-check": "tsc --noEmit"
}
}

The --noEmit flag checks types without generating .js output, since your build step handles compilation.

Type-Checking in Your Workflow​

Add a type-check job to your workflow:

name: React CI

on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]

jobs:
type-check:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Install dependencies
run: npm ci

- name: Run TypeScript type-check
run: npm run type-check

lint:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Install dependencies
run: npm ci

- name: Run ESLint
run: npm run lint

build:
runs-on: ubuntu-latest
needs: [type-check, lint]

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Install dependencies
run: npm ci

- name: Build React app
run: npm run build

The build job now depends on both type-check and lint, ensuring builds only proceed after all checks pass.

Example: Type Error Detection​

Suppose a developer writes a React component with a type error:

interface UserProps {
id: number;
name: string;
}

function UserCard({ id, name }: UserProps) {
return (
/* Error: name is string, but toUpperCase() needs to be called correctly */
<div>{name.toUppercase()}</div>
);
}

The TypeScript compiler would emit:

error TS2551: Property 'toUppercase' does not exist on type 'string'.

Running npm run type-check in CI would fail the job and prevent the code from merging, forcing the developer to fix the typo to toUpperCase().

Strict Mode vs. Standard Mode​

TypeScript supports varying levels of strictness. Strict mode enforces the most comprehensive checks:

SettingDescriptionBenefit
strict: falseLoose checking, allows implicit anyEasier onboarding, legacy support
strict: trueAll strict options enabledMaximum type safety, catches more bugs
Per-rule strict settingsFine-grained controlGradual migration to strict

For new React projects, always use strict: true. For existing projects, consider using skipLibCheck: true to skip checking third-party library types while still enforcing strict checking on your code.

Handling Type Errors with Comments​

Sometimes you need to suppress a type error intentionally (e.g., working with untyped third-party code). Use a TypeScript ignore comment:

/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
const data: any = JSON.parse(unknownInput);

Document why the suppression exists so future developers understand the intent.

Key Takeaways​

  • Type-checking in CI catches type errors automatically, preventing unsafe code from reaching production
  • Use tsc --noEmit in a separate CI job so type errors are visible before builds
  • Enable strict: true in tsconfig.json to enforce comprehensive type safety
  • Order your CI jobs logically: type-check and lint before building, so failures are caught early
  • TypeScript types provide free, automated testing of function signatures and data shapes

Frequently Asked Questions​

Can I type-check TypeScript without building?​

Yes, use tsc --noEmit to check types without generating JavaScript output. This is fast and doesn't require your build step.

What if a library doesn't have type definitions?​

Install the @types/library-name package if available (DefinitelyTyped has types for most popular libraries). If no types exist, you can either write .d.ts definition files or use skipLibCheck: true to ignore library type errors.

How do I suppress type errors in specific files?​

Add a triple-slash comment at the top of the file:

// @ts-nocheck

This disables all type checking for that file. Use sparingly; document the reason.

Should I use any type?​

Avoid any. It defeats the purpose of TypeScript. If a type is unknown, use unknown and narrow it with type guards:

// Bad: defeats type safety
const data: any = fetch(...);

// Good: requires narrowing before use
const data: unknown = fetch(...);
if (typeof data === 'string') {
console.log(data.toUpperCase());
}

Further Reading​