The first time you clone a production repo, you scroll past the src/ folder and freeze. There is a parade of files you have never opened: .eslintrc, .huskyrc, tsconfig.json, vite.config.js. Most of them start with a dot, like they are trying to hide. If I delete one of these, does the whole thing explode?

config files

For a long time, developers treat these files like furniture in someone elses house: do not touch, do not move, do not ask. Just write your feature and get out.

Then you start building things from scratch. And you realize something uncomfortable: most of what makes a codebase professional does not live in the code. It lives in these files; the ones nobody talks about in tutorials, the ones that quietly decide how your team writes code, ships code and avoids breaking each others work.

Here is a tour of the 15 files you should understand. Not a reference dump. A mental model for what each one does for you.

The “Do Not Get Fired” Files

1. .env

Your environment variables live here: API keys, database URLs, secret tokens; anything you do not want hardcoded.

Rule one of .env: it never, ever goes to GitHub. The number of leaked AWS keys floating around because someone committed a .env is a well-known problem. There are bots that scan public repos for them. Your secret has a half-life measured in minutes.

For more on securing your Node.js app and handling secrets properly, see the Node.js Security Checklist.

Pair it with a .env.example that has the same keys but no values, so teammates know what they need to set.

# .env
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
API_KEY=sk_live_1234567890abcdef
SECRET_TOKEN=your-secret-token-here

# .env.example
DATABASE_URL=
API_KEY=
SECRET_TOKEN=

Note:- Always validate your environment variables; see Input Validation and Sanitization for patterns.

2. .gitignore

The bouncer for your repo. Tells Git what to never track: node_modules, .env, build artifacts, OS junk like .DS_Store.

A bad .gitignore is how node_modules (300MB+ of dependencies) ends up in your commit history. A good one is the difference between a clean repo and a forensic recovery project.

# .gitignore
node_modules/
dist/
build/
.env
.env.local
*.log
.DS_Store
.vscode/
.idea/
coverage/

3. .dockerignore

Same idea, but for Docker builds. When Docker copies your project into a container, you do not want it dragging along your node_modules, .git, or local logs. A tight .dockerignore makes builds faster, smaller and less likely to accidentally bake secrets into an image you push to a registry.

# .dockerignore
node_modules
npm-debug.log
.git
.gitignore
README.md
.env
.env.local
.vscode
.idea
*.md

The “We All Run the Same Thing” Files

These exist because of one universal truth: “works on my machine” is not a deployment strategy.

4. package-lock.json

When you run npm install, npm picks specific versions of every dependency and every dependency is dependency. The lockfile records exactly what got picked.

Without it, two developers running npm install on the same package.json can end up with subtly different versions of some sub-dependency and now one persons tests pass and the other is do not. Commit the lockfile. Always.

// package-lock.json (excerpt)
{
  "name": "my-project",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "node_modules/lodash": {
      "version": "4.17.21",
      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
      "integrity": "sha512-sha512..."
    }
  }
}

5. .nvmrc

A one-line file that pins your Node.js version. If your project needs Node 20, .nvmrc says so and anyone with nvm can run nvm use and instantly match.

This tiny file kills a whole class of “why is this breaking on my machine” Slack threads.

# .nvmrc

When you switch to the directory, run:

nvm use
# Output: Now using node v20.11.0 (npm v10.2.4)

Or install automatically:

# Add to your .bashrc or .zshrc
autoload -U add-zsh-hook
load-nvmrc() {
  if [[ -f .nvmrc ]]; then
    nvm use > /dev/null 2>&1
  fi
}
add-zsh-hook chpwd load-nvmrc
load-nvmrc

If you want a deeper dive on .nvmrc and nvm setup, see my guide on Managing Node.js Versions with nvm.

The “Stop Arguing About Whitespace” Files

6. .editorconfig

A neutral peace treaty. Defines indent style, line endings, trailing whitespace rules. Every major editor (VS Code, IntelliJ, Vim, Sublime) reads it. It is the lowest-common-denominator way to make sure your codebase looks the same regardless of who opened it.

# .editorconfig
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

7. .eslintrc (or eslint.config.js)

Linting config. ESLint catches bugs and enforces patterns before they hit your PR: unused variables, missing returns, accidental == instead of ===, that sort of thing. Configured well, it is a free senior engineer reviewing every keystroke.

// eslint.config.js
module.exports = {
  env: {
    browser: true,
    es2021: true,
    node: true
  },
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended'
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 'latest',
    sourceType: 'module'
  },
  plugins: ['@typescript-eslint'],
  rules: {
    'no-unused-vars': 'error',
    'no-console': 'warn',
    '@typescript-eslint/explicit-function-return-type': 'warn'
  }
};

8. .prettierrc

Auto-formatting. Prettier does not care about your opinions on tabs vs spaces or single vs double quotes. It picks one, applies it ruthlessly and ends the debate forever. The whole team writes whatever, hits save and the file comes out identical.

// .prettierrc
{
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "es5",
  "printWidth": 100,
  "bracketSpacing": true
}

9. .prettierignore

The companion file that tells Prettier where not to format: generated files, vendored code, build outputs. Without it, you will watch Prettier reformat a 50,000-line minified bundle and wonder why your editor froze.

# .prettierignore
dist/
build/
coverage/
node_modules/
*.min.js
package-lock.json

The “Make the Tools Behave” Files

10. tsconfig.json

The TypeScript compiler is instruction manual. Strictness levels, module resolution, target JS version, path aliases; all of it lives here.

A loose tsconfig makes TypeScript barely better than JavaScript with extra steps. A strict one ("strict": true and friends) makes it the type-safety net it is actually supposed to be. This file is worth understanding line by line.

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": ["ES2020"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "moduleResolution": "node",
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "**/*.spec.ts"]
}

See also: TypeScript Best Practices.

11. .babelrc

Babel transpiles modern JavaScript into something older browsers and runtimes can understand. .babelrc tells it which presets and plugins to use: React JSX, async/await for ancient targets, experimental syntax and so on.

You will touch this less these days (Vite, esbuild and SWC are eating Babel is lunch), but legacy projects still run on it. Knowing what is inside is the difference between “the build broke” and “I know exactly which preset to swap.”

// .babelrc
{
  "presets": [
    ["@babel/preset-env", {
      "targets": {
        "browsers": [">0.25%", "not dead"]
      }
    }],
    "@babel/preset-react",
    "@babel/preset-typescript"
  ],
  "plugins": [
    "@babel/plugin-proposal-class-properties",
    "@babel/plugin-proposal-optional-chaining"
  ]
}

12. tailwind.config.js

If you use Tailwind, this is where you define your design system: custom colors, spacing scale, fonts, breakpoints, plugin extensions. Without configuring it, you are just using default Tailwind. With it, you are encoding your brand into the framework itself.

// tailwind.config.js
module.exports = {
  content: [
    "./src/**/*.{js,jsx,ts,tsx}",
    "./public/index.html"
  ],
  theme: {
    extend: {
      colors: {
        primary: {
          50: '#f0f9ff',
          100: '#e0f2fe',
          500: '#0ea5e9',
          900: '#0c4a6e'
        }
      },
      fontFamily: {
        sans: ['Inter', 'system-ui', 'sans-serif']
      },
      spacing: {
        '18': '4.5rem',
        '22': '5.5rem'
      }
    }
  },
  plugins: [
    require('@tailwindcss/forms'),
    require('@tailwindcss/typography')
  ]
};

13. vite.config.js

Vite is brain. Plugins, dev server settings, build optimizations, environment handling, path aliases. If your app starts slow, your imports look weird, or your build is bloated, the answer is almost always somewhere in this file.

// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
      '@components': path.resolve(__dirname, './src/components'),
      '@utils': path.resolve(__dirname, './src/utils')
    }
  },
  server: {
    port: 3000,
    proxy: {
      '/api': {
        target: 'http://localhost:4000',
        changeOrigin: true
      }
    }
  },
  build: {
    outDir: 'dist',
    sourcemap: true,
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom'],
          utils: ['lodash', 'date-fns']
        }
      }
    }
  }
});

14. next.config.js

Next.js is control room. Redirects, rewrites, image optimization domains, experimental flags, output mode (static export vs server vs edge), internationalization. The lot. As your Next app grows, this file grows with it.

// next.config.js
/** @type {import('next').NextConfig} */
module.exports = {
  reactStrictMode: true,
  images: {
    domains: ['res.cloudinary.com', 'images.unsplash.com'],
    formats: ['image/avif', 'image/webp']
  },
  async rewrites() {
    return [
      {
        source: '/api/:path*',
        destination: '/api/:path*'
      }
    ];
  },
  async redirects() {
    return [
      {
        source: '/old-page',
        destination: '/new-page',
        permanent: true
      }
    ];
  },
  experimental: {
    serverActions: true
  }
};

The “Catch Bugs Before They Catch You” Files

15. .huskyrc (or husky.config.js)

Husky lets you run scripts on Git hooks. Most commonly: “lint and run tests before allowing a commit,” or “block a push if the typecheck fails.”

It is a tiny file with a big cultural effect on a team. Once it is in place, a whole class of “oops, I committed broken code” mistakes just stops happening, because the broken code never makes it past the developer is own machine.

// .huskyrc
{
  "hooks": {
    "pre-commit": "lint-staged",
    "commit-msg": "commitlint --edit $1"
  }
}
// lint-staged.config.js
module.exports = {
  '*.{js,jsx,ts,tsx}': [
    'eslint --fix',
    'prettier --write',
    'jest --bail --findRelatedTests'
  ],
  '*.{json,md,yml,yaml}': [
    'prettier --write'
  ]
};

Other Essential Files

16. .npmrc

npm configuration scoped to your project. Useful for registry settings, authentication and default install behavior.

# .npmrc
registry=https://registry.npmjs.org/
legacy-peer-deps=true
save-exact=true
engine-strict=true

17. .npmignore

Like .gitignore, but for npmpackage publishing. Excludes files from being published to the npm registry.

# .npmignore
src/
tests/
*.spec.ts
*.test.ts
coverage/
.vscode/
.git/
.gitignore

18. .prettierrc.json / .prettierrc.yaml

Some teams prefer the JSON or YAML extension for Prettier config for better editor support and validation.

Configuration Files That Depend on Your Stack

Depending on your framework, language and build system, you might also encounter:

Build and Bundling

  • webpack.config.js – Webpack configuration (legacy, increasingly replaced by Vite/esbuild/swc)
  • rollup.config.js – Rollup for libraries and bundling
  • esbuild.config.js – Fast Go-based bundler

Testing

  • jest.config.js – Jest test runner
  • vitest.config.ts – Vitest (Vite-native testing)
  • pytest.ini – Python pytest config
  • pyproject.toml – Python project metadata

Python

  • pyproject.toml – Modern Python project config
  • setup.py / setup.cfg – Python package metadata
  • poetry.lock – Poetry dependency lock
  • requirements.txt – pip dependencies

Go

  • go.mod / go.sum – Go module definitions

Docker

  • Dockerfile – Container definition
  • docker-compose.yml – Multi-container orchestration

Best Practices (pinned)

  • Commit lockfiles (package-lock.json, yarn.lock, poetry.lock) to ensure consistent installs
  • Use .env.example or .env.template so teammates know required environment variables
  • Keep .gitignore comprehensive; review it when adding new dependencies or tooling
  • Enable strict mode in TypeScript (tsconfig.json) for actual type safety
  • Use husky to run linters and tests before commits, not after
  • Pair Prettier with ESLint; let Prettier handle formatting, let ESLint handle code quality
  • Use .nvmrc or .node-version to pin Node.js version per project
  • Review .dockerignore before every Docker build to avoid credential leaks

Quick Setup Checklist

  • Initialize project with npm init or your framework CLI
  • Add .env to .gitignore immediately
  • Create .env.example with placeholder values
  • Configure .editorconfig for consistent formatting
  • Set up ESLint with appropriate presets
  • Configure Prettier to work with ESLint
  • Pin Node.js version in .nvmrc
  • Add essential Git hooks with husky
  • Verify lockfile is committed
  • Review .dockerignore before first build
  • Document required configs in README

Common Mistakes to Avoid

  • Committing .env with real credentials
  • Forgetting to commit lockfiles
  • Using global ESLint/Prettier instead of project-level configs
  • Ignoring .nvmrc and using system Node.js version
  • Leaving husky unconfigured after initializing
  • Not testing build process locally (Docker)
  • Using different IDEs without .editorconfig

The Real Lesson

Every one of these files is an agreement. .editorconfig is an agreement about how the code looks. package-lock.json is an agreement about what versions you run. .huskyrc is an agreement about what is allowed to enter the repo. .env is an agreement about what stays out of it.

Source code is what your app does. These files are how your team works.

You do not need to memorize every flag in every config. Nobody does. But you should be able to open any one of them, understand what it is controlling and edit it without fear. That is the line that separates “writes code” from “is a developer”; not framework count, not years of experience. Just: do you know the scaffolding holding your project up?

If you have been treating these files like furniture in someone elses house, open one today. Read it. Change something small. Watch what happens! You will be surprised how much of your codebase you have been walking past.


If this was useful, the easiest way to test yourself is to start a new project from scratch and configure each one of these by hand at least once. The first time takes an afternoon. After that, you will never look at a repo the same way again.