TL;DR: If we include all commonly used .gitignore rules from public sources like GitHub’s gitignore repo, what remains — is likely to be only the truly essential, portable, version-controlled files that developers across all stacks have agreed are necessary.
The Question about Dotfiles
I was browsing another repository and wondered — why are there so many files starting with a dot (.)? Dotfiles are hidden by default on Unix-like systems. But why must I enable hidden files in my file manager just to work with important project settings?
What are these dotfiles, and why do they remain nearly identical across so many projects?
Today, let’s take the .gitignore file in a typical web application as a practical example.
Usually, we put to .gitignore files and folders, what we don't want to keep in repository in some reasons: temporary files, binaries, compiled code and compiled or generated artifacts. Why? Because idempotency plays a significant role here — reinforcing the KISS and DRY principles!
These are the files that developers almost universally agree should be versioned in web projects:
- Source code: index.html, .html; .css, .scss (unless auto-compiled); .js, .ts, .tsx, .jsx (raw source); src/, app/, components/, pages/, etc.
💡 These are hand-written assets, not built/generated.
- Project config
- package.json, pnpm-lock.yaml, yarn.lock
- vite.config.js, webpack.config.js, etc.
- tsconfig.json, jsconfig.json
- .eslintrc, .prettierrc, .babelrc
- postcss.config.js, tailwind.config.js, etc.
💡 These define how the project is built or linted. They’re not environment-specific.
- Static assets
- public/ folder (with raw favicon.ico, manifest.json, robots.txt)
- assets/ with .svg, .jpg, .png, etc.
- fonts like .woff, .woff2
💡 If added by hand or by design, they’re kept.
- Documentation & metadata
- README.md, LICENSE, CHANGELOG.md
- .gitignore itself
- docs/ (if not auto-generated)
- .nvmrc, .tool-versions, .editorconfig
💡 Used for human consumption or tooling guidance.
🧠 Meta Insight
Using a globally shared .gitignore template effectively acts like a crowdsourced filter. By using the union of thousands of developers’ ignore patterns across platforms and languages, you end up with a minimalist, but universal project core — a distillation of “what matters most to ship and share.”
🌐 Universal Shape of a Web Project
my-web-project/
├── src/ # 📝 Source code (frontend or backend logic)
│ ├── components/ # UI or functional modules
│ ├── pages/ # Route-based views (common in frameworks)
│ ├── api/ # API routes or handlers
│ ├── index.(js|ts) # App entry point
│ └── ... # Other domain-specific logic
│
├── build/ # 🚧 Any output (compiled code, static, etc.; ignored)
│ ├── index.html
│ ├── favicon.ico
│ ├── logo.svg
│ └── manifest.json
│
├── tests/ or test/ # 🧪 Unit/integration test code
├── scripts/ # 🔨 Utility scripts, for deployment, etc.
├── config/ # ⚙️ Configuration files (non-secrets)
├── docs/ # 📚 Documentation, guides, decisions
├── assets/ # 𝚨 Fonts, images, videos, raw static data
├── styles/ # 💅 CSS/SCSS modules or utility-first styles
│ └── main.(css|scss)
│
├── vendor/ # 📦 Downloaded packages (e.g., node_modules/, .venv/, etc.; ignored)
│
├── package.json / requirements.txt / pom.xml / Gemfile / composer.json / go.mod / Cargo.toml / Dockerfile / docker-compose.yml # Project metadata and dependencies
├── .*lintrc.* / .prettierrc / etc. # 🪮 Linter config files
├── .env.example # Example for environment variables
├── *config.js / Makefile / build.gradle / Gruntfile # 📋 Build & Tooling Configuration
├── .gitignore # (optional or empty — global ignore used)
├── README.md # 🏠 Overview, quick start
└── ...
🌍 How build/ fits major ecosystems
- Node.js / TS: src/ as code, build/ for tsc output
- Next.js: src/pages, next.config.js, build/ used
- React (Vite): src/, build/ or dist/ supported
- Python: src/, build/ via setuptools
- Go: Go prefers flat layout, but src/ is ok for monorepos
- Java: Maps to src/ and build/ (Gradle/Maven)
- Rust: Slightly different, but can still use src/, target/ (like build)
- Django: Commonly uses a project/ directory or src/ layout in modern setups
🌍 How vendor/ fits major ecosystems
- Node.js: vendor/node_modules/ ✅ (can be symlinked)
- Python: vendor/.venv/ ✅
- Go: vendor/ already standard ✅
- Rust: Uses target/ & Cargo.lock (No need to change)
- PHP (Composer): vendor/ already standard ✅
- Java: uses vendor/ for deps ✅
General Idea
If you strip away everything developers agree not to commit — across OSes, languages, editors, and tools — you’re left with a clear essential core that defines the global average web project:
A small, clean, organized set of hand-written source files, static assets, configuration, and documentation.
This shape is nearly universal — from Tokyo to Berlin, from Django to Vite.
Advice on Keeping .gitignore Clean
While project-level .gitignore files may still be necessary due to edge cases or legacy structures, we can minimize their complexity and keep them clean by adopting consistent naming conventions for ignorable artifacts and standardizing directory structures
Universal Global ~/.gitignore:
# OS
.DS_Store
Thumbs.db
# Editors
.idea/
.vscode/
*.swp
# Dependencies
vendor/
vendor/**
vendor/.*
vendor/**/.*
__pycache__/
# Builds
build/
dist/
coverage/
*.log
*.tmp
*.cache
.cache/
# Environments
.env
.env.*
*.local
# Runtime
*.pid
*.lock
git config --global core.excludesfile ~/.gitignore
Using consistent naming conventions for things like temporary files, build outputs, and automatically generated directories is a key strategy for maintaining a clean and predictable .gitignore file.
Here's advice on how to achieve this:
Adopt Standard Directory Names for Common Ignorable Output
Use Consistent File Extensions for Temporary Files: If temporary files must be created outside a designated temp directory, use a consistent, easily ignorable extension (e.g., .tmp, .bak). However, preferring a tmp/ directory is usually cleaner.
Centralize Ignorable Content: Structure your project so that files and folders that should be ignored are grouped together under predictable top-level or module-level directories. This allows you to use simple .gitignore rules like build/ or tmp/ instead of complex patterns trying to catch ignorable files scattered across the tree.
Leverage Tooling Conventions: Many build tools and frameworks have default directories for outputs, caches, etc. Using these defaults makes it easier to apply standard .gitignore templates provided by communities (like the ones on GitHub). Fighting against these conventions often leads to more complex, custom ignore rules.
Establish Team Conventions: Within a team or organization, agree on a set of preferred naming conventions for directories like build outputs (dist vs build), static asset staging (public vs static vs wwwroot), etc., where multiple options exist. Document these conventions.
Start with a Relevant Template: When starting a new project, don't write .gitignore from scratch. Find a template (e.g., from github/gitignore or gitignore.io) that matches your primary language, framework, and OS. These templates already incorporate best practices and common naming conventions.
By consciously adopting these naming and structuring conventions for the ignorable parts of your project, you make your .gitignore file:
Cleaner and Simpler: Fewer, more general rules (e.g., ignoring an entire build/ directory) are needed.
More Readable: It's easier to understand why certain things are ignored because their names clearly indicate their nature (build output, temp file, etc.).
More Maintainable: Changes in the project are less likely to require complex updates to the .gitignore if the underlying naming conventions remain consistent.
Leverage Tooling Conventions: Many build tools and frameworks have default directories for outputs, caches, etc. Using these defaults makes it easier to apply standard .gitignore templates provided by communities (like the ones on GitHub). Fighting against these conventions often leads to more complex, custom ignore rules.
Establish Team Conventions: Within a team or organization, agree on a set of preferred naming conventions for directories like build outputs (dist vs build), static asset staging (public vs static vs wwwroot), etc., where multiple options exist. Document these conventions.
Start with a Relevant Template: When starting a new project, don't write .gitignore from scratch. Find a template (e.g., from github/gitignore or gitignore.io) that matches your primary language, framework, and OS. These templates already incorporate best practices and common naming conventions.
By consciously adopting these naming and structuring conventions for the ignorable parts of your project, you make your .gitignore file:
Cleaner and Simpler: Fewer, more general rules (e.g., ignoring an entire build/ directory) are needed.
More Readable: It's easier to understand why certain things are ignored because their names clearly indicate their nature (build output, temp file, etc.).
More Maintainable: Changes in the project are less likely to require complex updates to the .gitignore if the underlying naming conventions remain consistent.
Top comments (1)
Nice posting! Can we talk when you are free?