Skip the fuss and see the full configuration.
In the past 2 years I’ve been improving the Neovim config I use everyday as a web developer, creating the ultimate Neovim config for working with TypeScript in Node, React, Angular and Astro projects.
While the ultimate Neovim config doesn’t exist, this attempts to be as close as possible. Everyone has their own preferences, and this article strives to give you the tools to create the ultimate config for you.
If you’re using Neovim for web development, you probably need:
eslint
.prettier
.*.ts
, *.js
, *.tsx
, *.jsx
, *.css
, *.scss
, *.html
, *.astro
files.This article guides you through achieving all of these, providing the ultimate developer experience. We’ll go through everything you’ll need, while minimizing dependencies and config size to make maintenance easy.
v0.10.4
. I tested this config on v0.10.4
and this is the version I recommend using with it. Some of the included
plugins require v0.10
or greater.Code completion is done with blink.cmp
. For snippets we
use rafamadriz/friendly-snippets
.
curl
is required for this plugin to work. If you do not have it installed you will get an error when starting Neovim.
{
"saghen/blink.cmp",
dependencies = { "rafamadriz/friendly-snippets", "nvim-lua/plenary.nvim" },
-- Use latest release tag pre-built binaries.
version = "v0.*",
-- `opts` is optional.
opts = {
keymap = {
-- 'super-tab' for mappings similar to vscode (tab to accept, arrow keys to navigate).
preset = "super-tab"
},
completion = {
documentation = {
-- Automatically show the documentation window when selecting a completion item.
auto_show = true,
},
}
}
}
Language servers provide code completion, diagnostics and code actions among other things.
For configuring the Astro language server we use nvim-lspconfig
.
The Astro language server must be installed separately with npm install --global @astrojs/language-server
.
{
"neovim/nvim-lspconfig",
dependencies = {
"saghen/blink.cmp"
},
config = function(_, opts)
local lspconfig = require("lspconfig")
local capabilities = require("blink.cmp").get_lsp_capabilities()
local servers = {
"astro",
}
for _, lsp in pairs(servers) do
lspconfig[lsp].setup {
capabilities = capabilities
}
end
end
},
We use typescript-tools.nvim
as our TypeScript language server. It performs better compared to other Typescript language servers, especially in large projects.
{
"pmizio/typescript-tools.nvim",
dependencies = { "nvim-lua/plenary.nvim", "neovim/nvim-lspconfig" },
},
none-ls
is used for configuring both auto format on save and diagnostics with prettier
and eslint
.
We are using prettierd
and eslint_d
instead of regular prettier
and eslint
for faster formatting.
prettierd and eslint_d
must be installed separately with npm install --global eslint_d @fsouza/prettierd
.
{
"nvimtools/none-ls.nvim",
dependencies = {
-- Required for eslint_d and prettierd sources.
"nvimtools/none-ls-extras.nvim",
},
config = function()
local augroup = vim.api.nvim_create_augroup("LspFormatting", {})
local null_ls = require("null-ls")
null_ls.setup({
sources = {
require("none-ls.diagnostics.eslint_d"),
require("none-ls.formatting.eslint_d"),
null_ls.builtins.formatting.prettierd,
}
-- Format on save.
-- Source: https://github.com/nvimtools/none-ls.nvim/wiki/Formatting-on-save
on_attach = function(client, bufnr)
if client.supports_method("textDocument/formatting") then
vim.api.nvim_clear_autocmds({ group = augroup, buffer = bufnr })
vim.api.nvim_create_autocmd("BufWritePre", {
group = augroup,
buffer = bufnr,
callback = function()
vim.lsp.buf.format({ async = false })
end,
})
end
end,
})
end
},
We use folke/snacks.nvim
for searching through references, definitions and type definitions of the item under the cursor.
It is fast and requires minimal conifguration. folke is great. we love folke.
{
"folke/snacks.nvim",
keys = {
{ "<leader>fd", function() Snacks.picker.lsp_definitions() end, desc = "Goto definition." },
{ "<leader>fr", function() Snacks.picker.lsp_references() end, nowait = true, desc = "References" },
{ "<leader>ft", function() Snacks.picker.lsp_type_definitions() end, desc = "Goto Type Definition" },
},
},
We use nvim-treesitter
for syntax highlighting.
{
"nvim-treesitter/nvim-treesitter",
-- Specify for lazy the main module to use for config() and opts().
-- Required as lazy can not figure this out for treesitter automatically.
main = "nvim-treesitter.configs",
opts = {
highlight = {
-- Enable the sytax highlighting module. All modules are disabled by default.
enable = true,
},
ensure_installed = {
"css",
"html",
"javascript",
"scss",
"tsx",
"typescript",
},
-- Automatically install missing parsers when entering buffer.
auto_install = true,
}
-- Lazy will execute this on install or update of the plugin.
-- This updates parsers when the plugin is updated or installed.
build = ":TSUpdate",
},
require("lazy").setup({
{
"saghen/blink.cmp",
dependencies = { "rafamadriz/friendly-snippets", "nvim-lua/plenary.nvim" },
-- Use latest release tag pre-built binaries.
version = "v0.*",
-- `opts` is optional.
opts = {
keymap = {
-- 'super-tab' for mappings similar to vscode (tab to accept, arrow keys to navigate).
preset = "super-tab"
},
completion = {
documentation = {
-- Automatically show the documentation window when selecting a completion item.
auto_show = true,
},
}
}
},
{
"neovim/nvim-lspconfig",
dependencies = {
"saghen/blink.cmp"
},
config = function(_, opts)
local lspconfig = require("lspconfig")
local capabilities = require("blink.cmp").get_lsp_capabilities()
local servers = {
"astro",
}
for _, lsp in pairs(servers) do
lspconfig[lsp].setup {
capabilities = capabilities
}
end
end
},
{
"pmizio/typescript-tools.nvim",
dependencies = { "nvim-lua/plenary.nvim", "neovim/nvim-lspconfig" },
},
{
"nvimtools/none-ls.nvim",
dependencies = {
-- Required for eslint_d and prettierd sources.
"nvimtools/none-ls-extras.nvim",
},
config = function()
local augroup = vim.api.nvim_create_augroup("LspFormatting", {})
local null_ls = require("null-ls")
null_ls.setup({
sources = {
require("none-ls.diagnostics.eslint_d"),
require("none-ls.formatting.eslint_d"),
null_ls.builtins.formatting.prettierd,
},
-- Format on save.
-- Source: https://github.com/nvimtools/none-ls.nvim/wiki/Formatting-on-save
on_attach = function(client, bufnr)
if client.supports_method("textDocument/formatting") then
vim.api.nvim_clear_autocmds({ group = augroup, buffer = bufnr })
vim.api.nvim_create_autocmd("BufWritePre", {
group = augroup,
buffer = bufnr,
callback = function()
vim.lsp.buf.format({ async = false })
end,
})
end
end,
})
end
},
{
"folke/snacks.nvim",
keys = {
{ "<leader>fd", function() Snacks.picker.lsp_definitions() end, desc = "Goto definition." },
{ "<leader>fr", function() Snacks.picker.lsp_references() end, nowait = true, desc = "References" },
{ "<leader>ft", function() Snacks.picker.lsp_type_definitions() end, desc = "Goto Type Definition" },
},
},
{
"nvim-treesitter/nvim-treesitter",
-- Specify for lazy the main module to use for config() and opts().
-- Required as lazy can not figure this out for treesitter automatically.
main = "nvim-treesitter.configs",
opts = {
highlight = {
-- Enable the sytax highlighting module. All modules are disabled by default.
enable = true,
},
ensure_installed = {
"css",
"html",
"javascript",
"scss",
"tsx",
"typescript",
},
-- Automatically install missing parsers when entering buffer.
auto_install = true,
},
-- Lazy will execute this on install or update of the plugin.
-- This updates parsers when the plugin is updated or installed.
build = ":TSUpdate"
}
})
telescope.nvim
is the predecessor of folke/snacks.nvim
.
It has very similar pickers for LSP find references / definitions / type definitions.
It’s downsides are that it’s fuzzy finder is slower and it’s config is slightly more verbose compared to snacks.nvim
.
conform.nvim
is a lightweight formatter plugin. It is probably the most popular formatter plugin. Still, I prefer using none-ls
as
configuring formatting sources is a mere require
call instead of specifying formatters manually for each filetype.
nvim-lint
is an async linter plugin. Similarly to conform.nvim
, I prefer
using none-ls
for linter diagnostics as configuring linters is easier and requires less configuration.