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.

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.
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:
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 Bersihinit.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.
-- 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")lazy.nvim dengan Reproducibilitasconfig/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):
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.
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:
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")-- 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" })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,
})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):
{
"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,
}{
"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,
}{
"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.
Sebelum sebuah config layak disebut daily driver, ia harus melewati seluruh checklist berikut. Adaptasi dari pelajaran seluruh seri:
| No | Area | Item | Referensi Episod | Status |
|---|---|---|---|---|
| 1 | Performa | Startup time < 100ms (target < 50ms) | 23 | ☐ |
| 2 | Lazy-loading | Semua plugin non-esensial punya trigger (event/cmd/keys/ft) | 23 | ☐ |
| 3 | Backup | undofile aktif + direktori undo dibuat otomatis | 7, 24 | ☐ |
| 4 | Keamanan | Tidak ada secret di config; .env.local di-gitignore | 24 | ☐ |
| 5 | Git | gitsigns gutter + blame + diffview untuk conflict | 18 | ☐ |
| 6 | Debugging | nvim-dap + nvim-dap-ui + debugger per bahasa | 22 | ☐ |
| 7 | Testing | neotest + adapter untuk bahasa yang dipakai | 26 | ☐ |
| 8 | AI | Copilot/Codeium/Avante terkonfigurasi (opsional) | 21 | ☐ |
| 9 | LSP | mason + lspconfig + keymaps gd/gr/K/<leader>rn | 15 | ☐ |
| 10 | Completion | nvim-cmp + snippets (LuaSnip + friendly-snippets) | 16 | ☐ |
| 11 | Formatting | conform format-on-save untuk semua bahasa utama | 17 | ☐ |
| 12 | Linting | nvim-lint asinkron + navigasi diagnostics [d/]d | 17 | ☐ |
| 13 | Navigasi | telescope (<leader>ff, <leader>fg) + oil | 11, 12 | ☐ |
| 14 | Terminal | toggleterm + lazygit terintegrasi | 19 | ☐ |
| 15 | Portabilitas | Config jalan di Linux + macOS + Windows dari satu repo | 24 | ☐ |
| 16 | Bootstrap | Script install idempotent untuk mesin baru | 24 | ☐ |
| 17 | Reproducibilitas | lazy-lock.json ter-commit | 9 | ☐ |
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.
Sebelum rekap, mari kita rangkum praktik terbaik yang tersebar di seluruh seri menjadi satu daftar yang bisa dipegang:
| # | Best Practice | Mengapa |
|---|---|---|
| 1 | Selalu modular: satu file per plugin | Mudah di-debug, mudah di-nonaktifkan (episode 9) |
| 2 | Lazy-load semuanya, tanpa kecuali | Startup tetap di bawah 50ms meski 50+ plugin (episode 23) |
| 3 | Manfaatkan text objects & motions dulu, baru plugin | Fondasi yang kuat lebih berharga daripada plugin canggih (episode 3) |
| 4 | Pelajari :help sebelum bertanya ke internet | Neovim punya dokumentasi luar biasa; jawaban sering ada di sana |
| 5 | Bangun workflow di atas Git | gitsigns, diffview, lazygit — kode yang ter-versioning itu aman (episode 18) |
| 6 | Uji setiap perubahan config | Kalau rusak, Git memungkinkan rollback cepat (episode 24) |
| 7 | Simpan secret di luar repo | Keamanan bukan fitur, tapi syarat mutlak (episode 24) |
| 8 | Ukur sebelum mengoptimasi | --startuptime dan :Lazy profile sebelum menyentuh config (episode 23) |
| 9 | Konsisten, jangan ganti keymap setiap minggu | Otot memori butuh stabilitas (episode 25) |
| 10 | Buat config kalian sendiri | Pemahaman adalah aset yang tidak bisa dibeli dari distro (episode 25) |
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:
| Fase | Episod | Materi Inti |
|---|---|---|
| Fondasi & Modal Editing | 0–4 | Setup environment, sejarah Neovim, modal editing, text objects, registers & macros |
| Windows, Buffer & Lua | 5–9 | Manajemen window/buffer, migrasi ke Lua, options & keymaps, autocmds, lazy.nvim |
| Visualisasi & Navigasi | 10–13 | Colorscheme & statusline, telescope, file explorer, flash & session management |
| Kemampuan IDE Modern | 14–18 | Treesitter, LSP & mason, completion, formatting & linting, integrasi Git |
| Workflow Lanjutan & AI | 19–22 | Terminal & task management, produktivitas editing, AI assistant, debugging dengan DAP |
| Performa, Distribusi, Testing | 23–26 | Profile & lazy-loading lanjutan, dotfiles & portabilitas, distro vs custom, neotest |
| Production Readiness | 27 | Arsitektur 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.
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:
Perjalanan kalian tidak berhenti di sini. Beberapa langkah berikutnya yang bisa kalian ambil untuk terus tumbuh:
:help secara mendalam. Neovim adalah alat dengan kedalaman hampir tak berujung; dokumentasi bawaan adalah kurikulum lanjutan kalian.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!