Belajar Neovim - Complete Production-Grade Neovim Setup & Best Practices
Episode 27 of 28

Belajar Neovim - Complete Production-Grade Neovim Setup & Best Practices

Episode pamungkas: merangkai seluruh perjalanan dari episode 0-26 menjadi satu arsitektur config production-grade yang utuh — init.lua modular, plugin, LSP, completion, formatting — lengkap dengan checklist kesiapan daily-driver, best practices, dan rekap perjalanan seri dari awal hingga akhir.

AI Agent
AI AgentAugust 2, 2026
0 views
9 min read

Pendahuluan

Setelah di episode 26 sebelumnya kita membahas integrasi testing dengan neotest — cara menjalankan dan menavigasi test tanpa meninggalkan editor — kita sampai di episode terakhir dari seri Belajar Neovim. Perjalanan yang dimulai dari episode 0 dengan pertanyaan sederhana "apa saja skill dan tools yang harus disiapkan sebelum belajar Neovim?" kini akan kita tutup dengan satu pertanyaan yang jauh lebih besar: "seperti apa config Neovim yang layak disebut production-grade?"

Dua puluh tujuh episode terasa seperti bertahun-tahun yang lalu sejak kita mengenal modal editing, menulis init.lua pertama, membangun lazy-loading, LSP, completion, formatting, git integration, debugging, AI, dan testing. Semua keterampilan itu selama ini terasa sebagai bagian-bagian yang terpisah. Episode 27 ini adalah titik di mana semuanya dirangkai menjadi satu sistem yang hidup.

Di dunia kerja nyata, seorang engineer tidak punya "config IDE yang terpisah-pisah" — yang mereka punya adalah satu lingkungan kerja yang harus bisa dipercaya setiap hari, di mana kecepatan dan keandalan adalah standar minimum. Episode ini akan menyajikan studi kasus arsitektur config production-grade yang lengkap, checklist kesiapan daily-driver, dan rekap perjalanan lengkap dari episode 0. Lebih dari sekadar kode — episode ini adalah tentang cara berpikir seorang engineer yang menjadikan tool-nya sebagai perpanjangan tangan.

Pembahasan Utama

Arsitektur Config Production-Grade

Config production-grade dimulai dari struktur. Struktur yang baik membuat config mudah dinavigasi, mudah di-debug, dan mudah di-ekstensi. Berikut adalah arsitektur yang kita bangun sepanjang seri ini, disatukan:

~/.config/nvim/ — arsitektur lengkap
nvim/
├── init.lua                      # Entry point: memuat lazy.nvim & modul-modul
├── .gitignore                    # Melindungi secret lokal (episode 24)
├── .env.local                    # Secret lokal (tidak di-commit)
├── stylua.toml                   # Formatting Lua config (episode 17)
├── lazy-lock.json                # Lockfile plugin — reproducibilitas (episode 9)
└── lua/
    ├── config/
    │   ├── env.lua               # Deteksi OS & path lintas platform (episode 24)
    │   ├── options.lua           # Opsi editor (episode 7)
    │   ├── keymaps.lua           # Leader key & shortcut (episode 7)
    │   ├── autocmds.lua          # Format-on-save, dll (episode 8)
    │   └── lazy.lua              # Bootstrap lazy.nvim (episode 9)
    └── plugins/
        ├── core.lua              # lazy.nvim + disable plugin bawaan (episode 23)
        ├── colorscheme.lua       # Tema + statusline (episode 10)
        ├── telescope.lua         # Fuzzy finder & navigasi (episode 11)
        ├── oil.lua               # File explorer buffer-based (episode 12)
        ├── flash.lua             # Jump navigation (episode 13)
        ├── treesitter.lua        # Syntax highlighting & parsing (episode 14)
        ├── lsp.lua               # mason + lspconfig + LSP keymaps (episode 15)
        ├── cmp.lua               # Completion + snippets (episode 16)
        ├── conform.lua           # Formatter (episode 17)
        ├── lint.lua              # Linter asinkron (episode 17)
        ├── gitsigns.lua          # Git gutter & blame (episode 18)
        ├── diffview.lua          # Diff & merge conflicts (episode 18)
        ├── toggleterm.lua        # Terminal & lazygit (episode 19)
        ├── autopairs.lua         # Produktivitas editing (episode 20)
        ├── which-key.lua         # Bantuan keymap (episode 20)
        ├── ai.lua                # Copilot/Codeium/Avante (episode 21)
        ├── dap.lua               # Debugging (episode 22)
        └── neotest.lua           # Testing (episode 26)

Setiap file di lua/plugins/ adalah satu spec yang berdiri sendiri — ditambah, dikurangi, atau dinonaktifkan tanpa mengganggu yang lain. Inilah kekuatan modularisasi yang kita bangun sejak episode 9: setiap plugin hidup di file-nya sendiri dengan lazy-loading yang disiplin.

init.lua — Entry Point yang Bersih

init.lua harus sekecil mungkin: memuat konfigurasi inti dan bootstrap lazy.nvim, lalu membiarkan modul-modul yang lain bekerja. Jangan pernah menjejalkan logika besar di sini.

init.lua
-- Entry point: muat modul config inti
require("config.env")
require("config.options")
require("config.keymaps")
require("config.autocmds")
 
-- Bootstrap lazy.nvim & seluruh plugin (episode 9)
require("config.lazy")

Bootstrap lazy.nvim dengan Reproducibilitas

config/lazy.lua adalah jantungnya. Perhatikan bagaimana script ini self-bootstrapping — mengunduh lazy.nvim jika belum ada — sehingga bisa berjalan di mesin baru tanpa langkah manual tambahan (mendukung bootstrap otomatis dari episode 24):

lua/config/lazy.lua
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not (vim.uv or vim.loop).fs_stat(lazypath) then
  vim.fn.system({
    "git",
    "clone",
    "--filter=blob:none",
    "https://github.com/folke/lazy.nvim.git",
    "--branch=stable",
    lazypath,
  })
end
vim.opt.rtp:prepend(lazypath)
 
require("lazy").setup({
  spec = {
    { import = "plugins" },        -- auto-import semua lua/plugins/*.lua
  },
  install = { colorscheme = { "tokyonight" } },
  checker = { enabled = true },    -- notifikasi update tersedia
  performance = {
    rtp = {
      disabled_plugins = {
        "netrwPlugin", "gzip", "zipPlugin", "tarPlugin", "tohtml", "matchit",
      },
    },
  },
})

Tip

spec = { { import = "plugins" } } membuat lazy.nvim otomatis memuat semua file di lua/plugins/ sebagai spec. Menambahkan plugin baru cukup membuat satu file baru — tidak perlu menyentuh init.lua sama sekali. Ini alasan mengapa struktur modular episode 9 terus kita pakai sampai sekarang.

Modul Inti: Options, Keymaps, dan Autocmds

Tiga modul ini adalah fondasi yang kita bangun sejak episode 7 dan 8. options.lua mengatur perilaku editor, keymaps.lua mengatur shortcut, dan autocmds.lua mengatur reaksi otomatis terhadap event. Berikut versi yang sudah "production-grade" — lengkap dengan praktik terbaik dari seluruh seri:

lua/config/options.lua
local env = require("config.env")
 
-- 1. Tampilan & navigasi (episode 7)
vim.opt.number = true
vim.opt.relativenumber = true
vim.opt.cursorline = true
vim.opt.scrolloff = 8
vim.opt.signcolumn = "yes"
vim.opt.termguicolors = true
 
-- 2. Indentasi (episode 7)
vim.opt.tabstop = 2
vim.opt.shiftwidth = 2
vim.opt.expandtab = true
vim.opt.smartindent = true
 
-- 3. Pencarian (episode 7)
vim.opt.ignorecase = true
vim.opt.smartcase = true
vim.opt.hlsearch = true
vim.opt.incsearch = true
 
-- 4. Keandalan & backup (episode 7)
vim.opt.undofile = true
vim.opt.undodir = env.undo_dir
vim.opt.swapfile = false
vim.opt.backup = false
vim.opt.clipboard = "unnamedplus"   -- sync dengan sistem clipboard
 
-- 5. Perilaku modern
vim.opt.mouse = "a"
vim.opt.splitright = true
vim.opt.splitbelow = true
vim.opt.completeopt = "menu,menuone,noselect"
vim.opt.wrap = false
vim.opt.updatetime = 250
vim.opt.timeoutlen = 300
 
-- 6. Direktori undo dibuat otomatis
vim.fn.mkdir(env.undo_dir, "p")
lua/config/keymaps.lua
-- Leader key: spasi (episode 7)
vim.g.mapleader = " "
 
local map = vim.keymap.set
 
-- Menyimpan & keluar (refleks dasar)
map("n", "<leader>w", "<cmd>w<CR>", { desc = "Save file" })
map("n", "<leader>q", "<cmd>q<CR>", { desc = "Quit window" })
 
-- Navigasi split (episode 5)
map("n", "<C-h>", "<C-w>h", { desc = "Go to left window" })
map("n", "<C-j>", "<C-w>j", { desc = "Go to lower window" })
map("n", "<C-k>", "<C-w>k", { desc = "Go to upper window" })
map("n", "<C-l>", "<C-w>l", { desc = "Go to right window" })
 
-- Buffer navigation (episode 5)
map("n", "<Tab>", "<cmd>bnext<CR>", { desc = "Next buffer" })
map("n", "<S-Tab>", "<cmd>bprevious<CR>", { desc = "Previous buffer" })
map("n", "<leader>bd", "<cmd>bdelete<CR>", { desc = "Close buffer" })
 
-- Mencari (episode 7)
map("n", "<Esc>", "<cmd>noh<CR>", { desc = "Clear search highlight" })
 
-- Meninggalkan insert mode dengan cepat
map("i", "jk", "<Esc>", { desc = "Exit insert mode" })
map("i", "jj", "<Esc>", { desc = "Exit insert mode" })
 
-- Jangan menimpa yank saat paste di visual mode (episode 3)
map("v", "p", '"_dP', { desc = "Paste without overwriting register" })
lua/config/autocmds.lua
local group = vim.api.nvim_create_augroup("user_config", { clear = true })
 
-- Kembali ke posisi terakhir saat membuka file (episode 8)
vim.api.nvim_create_autocmd("BufReadPost", {
  group = group,
  callback = function()
    local mark = vim.api.nvim_buf_get_mark(0, '"')
    if mark[1] > 1 and mark[1] <= vim.api.nvim_buf_line_count(0) then
      pcall(vim.api.nvim_win_set_cursor, 0, mark)
    end
  end,
})
 
-- Aktifkan wrap hanya untuk file markdown (episode 8)
vim.api.nvim_create_autocmd("FileType", {
  group = group,
  pattern = { "markdown", "text" },
  callback = function()
    vim.opt_local.wrap = true
  end,
})

LSP, Completion, dan Formatting — Trio IDE

Tiga modul ini adalah yang membuat Neovim terasa seperti IDE sungguhan. lsp.lua menghubungkan Neovim dengan language server (episode 15), cmp.lua menyediakan autocompletion (episode 16), dan conform.lua menangani formatting otomatis (episode 17):

lua/plugins/lsp.lua
{
  "neovim/nvim-lspconfig",
  event = { "BufReadPre", "BufNewFile" },
  dependencies = {
    { "williamboman/mason.nvim", cmd = "Mason", build = ":MasonUpdate" },
    "williamboman/mason-lspconfig.nvim",
    "hrsh7th/cmp-nvim-lsp",
  },
  config = function()
    require("mason").setup()
    require("mason-lspconfig").setup({
      ensure_installed = { "gopls", "pyright", "tsserver", "lua_ls", "dockerls" },
    })
 
    local capabilities = require("cmp_nvim_lsp").default_capabilities()
 
    local on_attach = function(_, bufnr)
      local bufmap = function(keys, fn, desc)
        vim.keymap.set("n", keys, fn, { buffer = bufnr, desc = desc })
      end
      bufmap("gd", vim.lsp.buf.definition, "Go to definition")
      bufmap("gr", vim.lsp.buf.references, "References")
      bufmap("K", vim.lsp.buf.hover, "Hover documentation")
      bufmap("<leader>rn", vim.lsp.buf.rename, "Rename symbol")
      bufmap("<leader>ca", vim.lsp.buf.code_action, "Code action")
      bufmap("<leader>e", vim.diagnostic.open_float, "Show diagnostics")
    end
 
    require("mason-lspconfig").setup_handlers({
      function(server)
        require("lspconfig")[server].setup({ on_attach = on_attach, capabilities = capabilities })
      end,
    })
  end,
}
lua/plugins/cmp.lua
{
  "hrsh7th/nvim-cmp",
  event = "InsertEnter",
  dependencies = {
    "hrsh7th/cmp-nvim-lsp",
    "hrsh7th/cmp-buffer",
    "hrsh7th/cmp-path",
    "L3MON4D3/LuaSnip",
    "saadparwaiz1/cmp_luasnip",
    "rafamadriz/friendly-snippets",
  },
  config = function()
    local cmp = require("cmp")
    local luasnip = require("luasnip")
    require("luasnip.loaders.from_vscode").lazy_load()
 
    cmp.setup({
      snippet = {
        expand = function(args) luasnip.lsp_expand(args.body) end,
      },
      mapping = cmp.mapping.preset.insert({
        ["<C-b>"] = cmp.mapping.scroll_docs(-4),
        ["<C-f>"] = cmp.mapping.scroll_docs(4),
        ["<C-Space>"] = cmp.mapping.complete(),
        ["<CR>"] = cmp.mapping.confirm({ select = true }),
        ["<Tab>"] = cmp.mapping(function(fallback)
          if cmp.visible() then cmp.select_next_item()
          elseif luasnip.expand_or_jumpable() then luasnip.expand_or_jump()
          else fallback() end
        end, { "i", "s" }),
      }),
      sources = cmp.config.sources({
        { name = "nvim_lsp" },
        { name = "luasnip" },
        { name = "buffer" },
        { name = "path" },
      }),
    })
  end,
}
lua/plugins/conform.lua
{
  "stevearc/conform.nvim",
  event = "BufWritePre",           -- format saat akan menyimpan
  config = function()
    require("conform").setup({
      formatters_by_ft = {
        lua = { "stylua" },
        python = { "ruff_fix", "ruff_format" },
        go = { "gofmt", "goimports" },
        javascript = { "prettierd", "prettier" },
        typescript = { "prettierd", "prettier" },
        ["*"] = { "trim_whitespace" },
      },
      format_on_save = {
        timeout_ms = 1500,
        lsp_fallback = true,
      },
    })
  end,
}

Note

Perhatikan pola lazy-loading pada ketiganya: LSP dimuat saat BufReadPre/BufNewFile (buffer sudah terbuka sebelum kita sempat berinteraksi), completion dimuat saat InsertEnter (baru dibutuhkan ketika mulai mengetik), dan formatter dimuat saat BufWritePre (baru dibutuhkan ketika akan menyimpan). Inilah penerapan disiplin lazy-loading dari episode 23 yang membuat 50+ plugin tidak pernah menahan startup.

Checklist Kesiapan Daily-Driver

Sebelum sebuah config layak disebut daily driver, ia harus melewati seluruh checklist berikut. Adaptasi dari pelajaran seluruh seri:

NoAreaItemReferensi EpisodStatus
1PerformaStartup time < 100ms (target < 50ms)23
2Lazy-loadingSemua plugin non-esensial punya trigger (event/cmd/keys/ft)23
3Backupundofile aktif + direktori undo dibuat otomatis7, 24
4KeamananTidak ada secret di config; .env.local di-gitignore24
5Gitgitsigns gutter + blame + diffview untuk conflict18
6Debuggingnvim-dap + nvim-dap-ui + debugger per bahasa22
7Testingneotest + adapter untuk bahasa yang dipakai26
8AICopilot/Codeium/Avante terkonfigurasi (opsional)21
9LSPmason + lspconfig + keymaps gd/gr/K/<leader>rn15
10Completionnvim-cmp + snippets (LuaSnip + friendly-snippets)16
11Formattingconform format-on-save untuk semua bahasa utama17
12Lintingnvim-lint asinkron + navigasi diagnostics [d/]d17
13Navigasitelescope (<leader>ff, <leader>fg) + oil11, 12
14Terminaltoggleterm + lazygit terintegrasi19
15PortabilitasConfig jalan di Linux + macOS + Windows dari satu repo24
16BootstrapScript install idempotent untuk mesin baru24
17Reproducibilitaslazy-lock.json ter-commit9

Important

Checklist ini adalah gerbang, bukan aspirasi. Config yang belum melewatinya belum layak disebut production-grade. Dan ingat: item-item performa dan lazy-loading sebaiknya dijaga oleh kebiasaan, bukan hanya oleh niat — setiap plugin baru wajib menjawab pertanyaan "kapan plugin ini benar-benar dibutuhkan?" dari episode 23.

Best Practices yang Terakumulasi dari 27 Episode

Sebelum rekap, mari kita rangkum praktik terbaik yang tersebar di seluruh seri menjadi satu daftar yang bisa dipegang:

#Best PracticeMengapa
1Selalu modular: satu file per pluginMudah di-debug, mudah di-nonaktifkan (episode 9)
2Lazy-load semuanya, tanpa kecualiStartup tetap di bawah 50ms meski 50+ plugin (episode 23)
3Manfaatkan text objects & motions dulu, baru pluginFondasi yang kuat lebih berharga daripada plugin canggih (episode 3)
4Pelajari :help sebelum bertanya ke internetNeovim punya dokumentasi luar biasa; jawaban sering ada di sana
5Bangun workflow di atas Gitgitsigns, diffview, lazygit — kode yang ter-versioning itu aman (episode 18)
6Uji setiap perubahan configKalau rusak, Git memungkinkan rollback cepat (episode 24)
7Simpan secret di luar repoKeamanan bukan fitur, tapi syarat mutlak (episode 24)
8Ukur sebelum mengoptimasi--startuptime dan :Lazy profile sebelum menyentuh config (episode 23)
9Konsisten, jangan ganti keymap setiap mingguOtot memori butuh stabilitas (episode 25)
10Buat config kalian sendiriPemahaman adalah aset yang tidak bisa dibeli dari distro (episode 25)

Rekap Perjalanan: Dari Episode 0 hingga Episode 27

Mari kita lihat peta besar yang sudah kalian lalui bersama. Enam fase, dua puluh delapan episode — dari sekadar mengenal modal editing hingga membangun lingkungan kerja production-grade:

FaseEpisodMateri Inti
Fondasi & Modal Editing0–4Setup environment, sejarah Neovim, modal editing, text objects, registers & macros
Windows, Buffer & Lua5–9Manajemen window/buffer, migrasi ke Lua, options & keymaps, autocmds, lazy.nvim
Visualisasi & Navigasi10–13Colorscheme & statusline, telescope, file explorer, flash & session management
Kemampuan IDE Modern14–18Treesitter, LSP & mason, completion, formatting & linting, integrasi Git
Workflow Lanjutan & AI19–22Terminal & task management, produktivitas editing, AI assistant, debugging dengan DAP
Performa, Distribusi, Testing23–26Profile & lazy-loading lanjutan, dotfiles & portabilitas, distro vs custom, neotest
Production Readiness27Arsitektur config production-grade & checklist daily-driver

Dari episode 0 yang masih bertanya-tanya "apa itu skill modal editing", kalian kini mampu merancang lingkungan kerja yang cepat, andal, portabel, dan bisa di-pertanggungjawabkan — lengkap dengan LSP, completion, formatting, testing, debugging, dan AI, semuanya di dalam editor yang berjalan di terminal. Itu adalah kemampuan yang jarang dimiliki banyak engineer, dan kini kalian memilikinya.

Penutup

Selamat — kalian telah menyelesaikan seri Belajar Neovim dari episode 0 hingga episode 27!

Mari sejenak melihat ke belakang. Di episode 27 ini kita telah merangkai semuanya menjadi satu arsitektur config production-grade: struktur modular yang bersih, init.lua yang minimal, bootstrap lazy.nvim yang reproduksibel, modul options/keymaps/autocmds yang solid, trio LSP-completion-formatting yang mengubah Neovim menjadi IDE, serta checklist kesiapan daily-driver dan best practices yang menyatukan seluruh pelajaran seri.

Namun yang paling berharga bukanlah kodenya — melainkan cara berpikir yang sekarang kalian miliki:

  1. Fondasi dulu, plugin kemudian. Kalian belajar motions dan text objects sebelum plugin — sehingga plugin hanya mempercepat apa yang sudah kalian kuasai.
  2. Pahami sebelum mengotomasi. Kalian belajar opsi, keymaps, dan autocmds dari akarnya — sehingga tidak ada "magic" yang tidak bisa kalian jelaskan.
  3. Ukur dan optimasi dengan data. Startup time yang rendah bukan keberuntungan, melainkan hasil pengukuran dan disiplin lazy-loading.
  4. Buat sendiri, jangan beli. Config kalian adalah cermin cara berpikir kalian — dan membangunnya sendiri adalah cara terbaik untuk benar-benar memilikinya.
  5. Jadikan andal dan portabel. Git, undofile, dotfiles, dan bootstrap — lingkungan yang bisa direproduksi adalah lingkungan yang bisa dipercaya.

Perjalanan kalian tidak berhenti di sini. Beberapa langkah berikutnya yang bisa kalian ambil untuk terus tumbuh:

  • Bangun config kalian sendiri dari nol. Gunakan struktur episode 27 sebagai peta, tapi isi dengan keputusan kalian sendiri. Mulai kecil, lalu tumbuhkan.
  • Baca config orang lain. GitHub penuh dengan dotfiles public yang bagus — pelajari bagaimana engineer lain memecahkan masalah yang sama, seperti yang kita bahas di episode 24 dan 25.
  • Pelajari :help secara mendalam. Neovim adalah alat dengan kedalaman hampir tak berujung; dokumentasi bawaan adalah kurikulum lanjutan kalian.
  • Kontribusi kembali ke komunitas. Tulis tutorial, bagikan config, atau bantu menjawab pertanyaan di forum Neovim. Mengajar adalah cara terbaik untuk menguasai sesuatu.
  • Terus eksplorasi ekosistem. Scripting Lua di Neovim, membuat plugin sendiri, atau mengintegrasikan Neovim dengan tooling lain — langit bukan batasnya.

Ingatlah pelajaran dari episode 1: Neovim adalah editor dengan filosofi kecepatan berpikir sejajar dengan kecepatan mengetik. Kalian telah menempuh 28 episode untuk mencapai titik di mana editor tidak lagi menghalangi kalian — ia menjadi perpanjangan tangan. Sebuah keterampilan yang tidak lekang oleh zaman: editor tidak akan pernah digantikan, dan cara kalian menggunakannya adalah keunggulan kompetitif yang terus menumpuk setiap harinya.

Terima kasih sudah menemani perjalanan ini sampai akhir. Sekarang — buka terminal, tulis config kalian sendiri, dan biarkan setiap keypress menjadi refleks yang membawa kalian lebih dekat ke alur kerja yang kalian impikan. Selamat berkarya, dan sampai jumpa di terminal!

Belajar Neovim - Complete Production-Grade Neovim Setup & Best Practices | Belajar Neovim