TNTStack
Architecture

Shared Configs

Shared ESLint and TypeScript configurations used across all workspaces.

Two config packages keep ESLint and TypeScript settings consistent across the monorepo. Every workspace extends one of their presets.

Directory Structure

base.js
next.js
react-internal.js
package.json
base.json
nextjs.json
react-library.json
package.json

Which Config Goes Where

WorkspaceESLintTypeScript
apps/nativenext.jsnextjs.json
apps/webnext.jsnextjs.json
packages/uireact-internal.jsreact-library.json
packages/corereact-internal.jsreact-library.json
packages/i18nbase.jsbase.json
packages/clibase.jsbase.json

ESLint Config (@workspace/eslint-config)

ESLint 9 flat config format. Three presets, each building on the previous:

packages/eslint-config/base.js
import js from "@eslint/js";
import eslintConfigPrettier from "eslint-config-prettier";
import onlyWarn from "eslint-plugin-only-warn";
import turboPlugin from "eslint-plugin-turbo";
import tseslint from "typescript-eslint";

export const config = [
  js.configs.recommended,
  eslintConfigPrettier,
  ...tseslint.configs.recommended,
  {
    plugins: { turbo: turboPlugin },
    rules: { "turbo/no-undeclared-env-vars": "warn" },
  },
  { plugins: { onlyWarn } },
  { ignores: ["dist/**", ".next/**"] },
];
PluginWhat it does
@eslint/jsESLint recommended ruleset
typescript-eslintTypeScript-aware rules
eslint-config-prettierDisables rules that conflict with Prettier
eslint-plugin-turboValidates Turborepo env var declarations
eslint-plugin-only-warnDowngrades all errors to warnings
packages/eslint-config/next.js
import js from "@eslint/js"
import pluginNext from "@next/eslint-plugin-next"
import eslintConfigPrettier from "eslint-config-prettier"
import pluginReact from "eslint-plugin-react"
import pluginReactHooks from "eslint-plugin-react-hooks"
import globals from "globals"
import tseslint from "typescript-eslint"

import { config as baseConfig } from "./base.js"

export const nextJsConfig = [
  ...baseConfig,
  js.configs.recommended,
  eslintConfigPrettier,
  ...tseslint.configs.recommended,
  {
    ...pluginReact.configs.flat.recommended,
    languageOptions: {
      ...pluginReact.configs.flat.recommended.languageOptions,
      globals: { ...globals.serviceworker },
    },
  },
  {
    plugins: { "@next/next": pluginNext },
    rules: {
      ...pluginNext.configs.recommended.rules,
      ...pluginNext.configs["core-web-vitals"].rules,
    },
  },
  {
    plugins: { "react-hooks": pluginReactHooks },
    settings: { react: { version: "detect" } },
    rules: {
      ...pluginReactHooks.configs.recommended.rules,
      "react/react-in-jsx-scope": "off",
      "react/prop-types": "off",
    },
  },
]

Extends base.js with Next.js plugin (recommended + core-web-vitals rules), React, React Hooks, and service worker globals.

Same as next.js with two differences:

  1. No @next/eslint-plugin-next — this isn't a Next.js app
  2. Adds globals.browser — since packages/ui components run directly in the browser

Used by packages/ui.

ESLint Usage

Each workspace imports the appropriate preset:

apps/web/eslint.config.js
import { nextJsConfig } from "@workspace/eslint-config/next";

export default [...nextJsConfig];
packages/ui/eslint.config.js
import { config } from "@workspace/eslint-config/react-internal";

export default [...config];

TypeScript Config (@workspace/typescript-config)

Three configs that extend each other:

packages/typescript-config/base.json
{
  "$schema": "https://json.schemastore.org/tsconfig",
  "display": "Default",
  "compilerOptions": {
    "declaration": true,
    "declarationMap": true,
    "esModuleInterop": true,
    "incremental": false,
    "isolatedModules": true,
    "lib": ["es2022", "DOM", "DOM.Iterable"],
    "module": "NodeNext",
    "moduleDetection": "force",
    "moduleResolution": "NodeNext",
    "noUncheckedIndexedAccess": true,
    "resolveJsonModule": true,
    "skipLibCheck": true,
    "strict": true,
    "target": "ES2022"
  }
}

strict: true + noUncheckedIndexedAccess enforces strict type checking. incremental: false is set because Turborepo handles caching.

packages/typescript-config/nextjs.json
{
  "$schema": "https://json.schemastore.org/tsconfig",
  "display": "Next.js",
  "extends": "./base.json",
  "compilerOptions": {
    "plugins": [{ "name": "next" }],
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "allowJs": true,
    "jsx": "preserve",
    "noEmit": true
  }
}

Overrides module resolution to Bundler (Next.js uses its own bundler), enables the Next.js TypeScript plugin for route type checking, and sets noEmit since Next.js handles compilation.

packages/typescript-config/react-library.json
{
  "$schema": "https://json.schemastore.org/tsconfig",
  "display": "React Library",
  "extends": "./base.json",
  "compilerOptions": {
    "jsx": "react-jsx"
  }
}

react-jsx transform means no import React needed in component files.

TypeScript Usage

apps/web/tsconfig.json
{
  "extends": "@workspace/typescript-config/nextjs.json"
}

Adding a new package? Extend react-internal.js + react-library.json for React packages, or base.js + base.json for plain TypeScript utilities.

On this page