Lazy.nvim Optimization
Diagnose and fix Neovim startup performance through profiling and targeted lazy-loading.
Profiling Workflow
When a user reports slow startup, follow this sequence — don't skip to solutions.
Step 1: Measure Baseline
nvim --startuptime startup.log && tail -1 startup.log
Compare against targets:
-
< 30ms: Excellent
-
30-50ms: Good
-
50-100ms: Acceptable
100ms: Needs work
Step 2: Identify Slow Plugins
:Lazy profile
Look for plugins with load time > 10ms. These are your optimization targets. Sort by time, not alphabetically.
Also check the startup log for:
-
Files taking > 10ms to source
-
setup() functions taking > 5ms
-
Synchronous operations blocking startup
Step 3: Apply Lazy-Loading
For each slow plugin, choose the right trigger:
Plugin Type Trigger Example
Has clear commands cmd
cmd = "Telescope"
Accessed via keybindings keys
keys = { "<leader>e" }
Language-specific ft
ft = { "rust", "go" }
Needed after UI renders event = "VeryLazy"
UI enhancements
Needed when editing event = "BufReadPost"
Git signs, diagnostics
Needed in insert mode event = "InsertEnter"
Completion, autopairs
Only used as dependency lazy = true
plenary.nvim
Step 4: Verify Improvement
nvim --startuptime startup-after.log && tail -1 startup-after.log
Compare total times. Then :Lazy profile to verify plugins load when expected.
What NOT to Lazy-Load
These need to load at startup — don't fight it:
-
Colorscheme — Set priority = 1000 so it loads first. Visible flash if deferred.
-
Treesitter — Needed for syntax highlighting immediately. Deferring causes flicker.
-
Statusline — Visible at startup. Deferring causes layout shift.
-
which-key — Only if it shows on startup. Otherwise can lazy-load.
-
LSP base setup — Though individual servers can lazy-load by filetype.
Common Bottlenecks
Synchronous system calls at startup:
-- Bad: blocks startup vim.fn.system("git status")
-- Good: defer it vim.defer_fn(function() vim.fn.system("git status") end, 100)
Loading all LSP servers at once: Consider loading LSP servers per-filetype instead of all at startup. Each server you don't load saves 5-15ms.
Plugins without any trigger: A bare { "plugin/name" } spec loads at startup. Always add a trigger unless the plugin genuinely needs immediate availability.
Performance Checklist
Startup
-
Colorscheme has priority = 1000
-
File explorers load on cmd/keys
-
Git interfaces load on cmd/keys
-
Completion loads on InsertEnter
-
Language plugins load on filetype
-
UI enhancements load on VeryLazy
Runtime
-
Telescope uses fzf-native extension
-
Treesitter parsers installed only for used languages
-
LSP servers only for filetypes you use
-
No plugins duplicating built-in 0.10+ features
Config
-
No synchronous system calls at startup
-
setup() only runs when plugins load
-
Keymaps defined with plugin lazy-loading
-
Auto-commands use specific events, not "*"
Reference Files
For detailed information:
-
references/lazy-loading-decision-tree.md
-
Decision tree for choosing lazy-loading strategy
-
references/profiling-guide.md
-
Advanced profiling techniques