TypeScript Best Practices

This document captures the various best practices and recommended tools for TypeScript that we prefer using at FPBlock, specifically when a greenfield project is involved.

Summary of Recommendations

CategoryEvaluated OptionsOur Recommendation
Node Version Managerfnm, nvmfnm
Package Managerpnpm, npm, Yarn, Bunpnpm
React FrameworkVite, Next.js, TanStack Start, RemixVite
BundlerVite default (Rolldown), Webpack, Parcel, RspackVite's default
TypeScript ConfigManual, bases@tsconfig/strictest
Linter & FormatterBiome, ESLint, Prettier, Oxlint, Bun/DenoBiome
Dead Code AnalysisKnipKnip
State ManagementTanStack Query, ReduxTanStack Query
Decimal Librarybig.js, decimal.js, bignumber.jsbig.js
Testing (Unit)JestJest
Testing (E2E)CypressCypress
DeploymentCloudflare PagesCloudflare Pages

Project Setup & Environment

Node Version Management

It is important to make sure that you pin the Node.js version in your project for reproducibility.

  • Node version manager: We highly recommend fnm (written in Rust) to download the appropriate Node version into your $PATH.
  • Pinning Node version: Specify this in your project's package.json file:
    "engines": {
      "node": "=22.16.0"
    }
    
  • Enforcing strict engine: Add the following to your .npmrc file in the project (create one if you don't have one):
    engine-strict=true
    
    This will result in a compile error if you don't have the appropriate Node version in your $PATH.

Package Managers

We lean towards using pnpm.

To ensure everyone uses the same package manager version:

  1. Pin your package manager in package.json:
    {
      "packageManager": "pnpm@10.30.3"
    }
    
  2. Enable corepack (it's not enabled by default as of now):
    corepack enable
    

The main benefit of using corepack is that it ensures the correct version is used by reading the packageManager field in your project's package.json. When you run pnpm install for the first time, it will ask about installing the appropriate version:

❯ pnpm install
! Corepack is about to download https://registry.npmjs.org/pnpm/-/pnpm-10.30.3.tgz
? Do you want to continue? [Y/n] y

Lockfile is up to date, resolution step is skipped
Already up to date
Done in 9.9s using pnpm v10.30.3

Note: pnpm also has a way to manage Node.js environments, but it doesn't include the binaries for Corepack.

Frameworks & Tooling

React Framework

For most of our client work, our web applications are built entirely for Client-Side Rendering (CSR) as opposed to Server-Side Rendering (SSR). Keeping things limited to CSR simplifies architecture significantly. Given this, Vite is our recommended tool.

Bundlers

Use the default bundler of your chosen React framework. Since Vite is our recommended React framework, relying on its bundler (transitioning to Rolldown from v8) is the best choice.

Code Quality & Standards

TypeScript Options

Go with centralized, community-curated TypeScript options instead of manually specifying them.

bases is a popular project providing various flavors of TypeScript options. We have been happy with their @tsconfig/strictest option for maximum type safety and recommend it for new and existing projects.

Linters & Formatters

We prefer Biome because:

  • It's a single tool that covers both linting and formatting (and acts as an LSP server, which is a plus).
  • It comes with built-in domain rules for popular projects like React without needing to rely on external plugins.

Dead Code Analysis

There is only one proper tool in town: Knip.

Testing

  • Unit Testing: Use Jest.
  • End-to-End Testing: Jan has had good experiences with Cypress.

Libraries

State Management

Use TanStack Query for managing server state. While it drastically reduces the need for traditional client state management libraries by handling asynchronous data, caching, and server state, it does not completely replace client state for UI-specific needs. For more details, refer to the official documentation: Does this replace client state?

You might also need global state management for certain client-side use cases, for which we recommend using Redux. It's perfectly fine to mix both TanStack Query (for server state) and Redux (for client state) based on the specific problem you are solving.

Decimal Libraries

If you need a decimal library, start with big.js as it's the most lightweight among the popular options. However, it lacks methods like exponential functions; in those cases, you might have to move to decimal.js.

Note: If you are using big.js, make sure to install @types/big.js for TypeScript definitions.

CI & Deployment

CI Checks

Make sure to have these mandatory checks in your CI:

  • Dead code analysis: knip
  • Linter and formatting check using Biome
  • TypeScript typecheck: tsc --noEmit

You can optionally have these additional checks enabled, but configured to exit successfully (exit code 0):

pnpm outdated || exit 0
pnpm audit || exit 0

The reason these aren't treated as hard failures is due to the high churn in the TypeScript ecosystem. It's good to be aware of outdated dependencies and have a weekly or biweekly process to clean them up.

Deployment Options

Unless there is a good reason not to, use Cloudflare Pages for deployment.

Tip: Invest some time to ensure that preview URLs work with the backend when you create a PR. Having functional preview URLs for your PRs is a huge quality-of-life improvement.