Understanding Config File Organization
Understanding Config File Organization
XDG Base Directory Specification
The XDG Base Directory Specification is a standard that defines where applications should store their configuration files, data, and cache on Unix-like systems (Linux, macOS).
Key directories:
| Variable | Default Location | Purpose | Example |
|---|---|---|---|
XDG_CONFIG_HOME | ~/.config/ | User-specific configurations | Shell configs, editor settings |
XDG_DATA_HOME | ~/.local/share/ | User-specific data files | Application state, databases |
XDG_CACHE_HOME | ~/.local/cache/ | Non-essential cached data | Downloads, thumbnails |
XDG_STATE_HOME | ~/.local/state/ | Logs, history, recent files | Command history, undo data |
Why this matters:
- Keeps home directory clean (no more 50 dotfiles in
~) - Makes backups easier (just backup
~/.config/and~/.local/) - Standardizes where tools look for configs
- Works seamlessly with dotfile managers like chezmoi
From your screenshot, you have:
~/.config/
├── zellij/
├── yarn/
├── zsh/ ← We'll set this up
├── nushell/ ← Future shell
├── fish/ ← Future shell
├── nvim/
├── karabiner/
├── htop/
├── gh/
├── env/
├── configstore/
└── chezmoi/
The conf.d Pattern
The conf.d/ pattern (configuration directory) is a modular approach where instead of one monolithic config file, you have a directory of smaller, focused files that get loaded automatically.
How it works:
~/.config/zsh/
├── .zshrc # Main loader file
└── conf.d/ # Modular configs
├── 00-environment.zsh # Env vars (PATH, EDITOR, etc.)
├── 10-aliases.zsh # Command aliases
├── 20-functions.zsh # Custom functions
├── 30-completion.zsh # Tab completion
├── 40-keybindings.zsh # Key mappings
└── 99-local.zsh # Machine-specific overrides
Why use numbered prefixes (00-, 10-, etc.)?
- Controls load order (environment vars before aliases)
- Alphabetical sorting ensures predictable behavior
- Easy to insert new configs between existing ones
- Convention: increment by 10 to leave room for additions
The main loader (.zshrc or init.zsh) typically looks like:
bash# Load all configs in order for config in ~/.config/zsh/conf.d/*.zsh; do [ -f "$config" ] && source "$config" done
Shell-Specific Config Locations
Different shells have different conventions, but modern shells support XDG:
Zsh
Traditional: ~/.zshrc, ~/.zprofile, ~/.zshenv
XDG-compliant:
~/.config/zsh/
├── .zshenv # Always sourced (set ZDOTDIR here)
├── .zprofile # Login shells
├── .zshrc # Interactive shells
└── conf.d/ # Modular configs
Key trick: Set ZDOTDIR in ~/.zshenv to point to ~/.config/zsh/, then zsh looks there for all other configs.
Nushell
Native XDG support:
~/.config/nushell/
├── config.nu # Main config
├── env.nu # Environment variables
└── scripts/ # Custom commands (nu files)
Nushell loads env.nu first, then config.nu. No need for conf.d pattern because you can use or source files explicitly:
nu# In config.nu source ~/.config/nushell/scripts/aliases.nu source ~/.config/nushell/scripts/custom-commands.nu
Fish
Native XDG support:
~/.config/fish/
├── config.fish # Main config
├── fish_variables # Universal variables
└── conf.d/ # Auto-loaded configs
├── aliases.fish
├── environment.fish
└── functions.fish
Fish automatically sources all files in conf.d/ in alphabetical order. No manual loading needed.
Why Modular > Monolithic
Monolithic approach (old way):
bash# ~/.zshrc (500 lines) export PATH="..." export EDITOR="..." alias ls="eza" alias cat="bat" function myfunc() { ... } # ... 400 more lines
Problems:
- Hard to find specific settings
- Merge conflicts when syncing between machines
- Can't easily disable one section
- Comments become messy
Modular approach (modern way):
conf.d/
├── 00-environment.zsh # 20 lines of env vars
├── 10-aliases.zsh # 30 lines of aliases
├── 20-functions.zsh # Custom functions
└── 99-local.zsh # Machine-specific (git-ignored)
Benefits:
- Find settings instantly (need to change alias? →
10-aliases.zsh) - Disable a module by renaming it (
.zsh→.zsh.disabled) - Share common configs across shells (environment.zsh → environment.fish)
- Git history shows which module changed
- Machine-specific configs can be git-ignored or templated
How This Works with Chezmoi
Chezmoi manages your dotfiles in ~/.local/share/chezmoi/ and syncs them to their target locations.
For ~/.config/zsh/, chezmoi would store:
~/.local/share/chezmoi/
└── dot_config/
└── zsh/
├── dot_zshrc
└── conf.d/
├── 00-environment.zsh
├── 10-aliases.zsh
└── 99-local.zsh.tmpl # Template for machine-specific
When you run chezmoi apply, it creates:
~/.config/zsh/
├── .zshrc
└── conf.d/
├── 00-environment.zsh
├── 10-aliases.zsh
└── 99-local.zsh # Generated from template
Templates let you customize per-machine:
bash# 99-local.zsh.tmpl {{- if eq .chezmoi.hostname "macbook-pro" }} export WORK_MODE=true {{- else }} export WORK_MODE=false {{- end }}
Recommended Structure for You
Based on your current shell-config/zsh/ files:
New structure:
~/.config/zsh/
├── .zshrc # Main loader (sources conf.d/*)
└── conf.d/
├── 00-environment.zsh # Your current environment.zsh
├── 10-aliases.zsh # Your current aliases.zsh
├── 20-display.zsh # Your current display.zsh
└── 99-local.zsh # Machine-specific (optional)
Your existing .zshrc stays at ~/.zshrc (because zsh expects it there unless you set ZDOTDIR), but it will source from the new location:
bash# ~/.zshrc # Load modular configs for config in ~/.config/zsh/conf.d/*.zsh; do [ -f "$config" ] && source "$config" done # Remaining zsh-specific setup (asdf, zoxide, starship) # ...
Summary
XDG: Standard locations (~/.config/, ~/.local/share/, etc.)
conf.d: Modular files auto-loaded in alphabetical order
Numbered prefixes: Control load order, leave room for growth
Chezmoi: Manages these files, syncs across machines
Your new workflow:
- Edit configs in
~/.config/zsh/conf.d/10-aliases.zsh - Chezmoi tracks changes automatically
- Push to Git, pull on other machines
- All shells (zsh, nushell, fish) use same XDG structure
Next steps:
- Migrate
shell-config/zsh/→~/.config/zsh/conf.d/ - Add to chezmoi management
- Set up similar structure for nushell, fish when you switch shells