At all my jobs, I've always been an Apple user. I've owned a 2009 MacBook, a 2017 Pro, an M2 Pro, a fully spec'd M3 Pro, and an M3 Air. The displays, battery life, and build quality are undeniableβbut eventually, macOS started feeling... sluggish.
Taming macOS animations, dealing with Mission Control quirks, and the lack of a proper tiling window manager made me crave something faster. The switch to Linux unlocked a level of control and immediacy I didn't know I was missing.
Of course, migrating to Linux involves tinkering. I built an evolving dotfiles repo of scripts and tooling that shape my workflow. You can check it out on my GitHub (if it's public!).
My setup is now keyboard-first; the mouse is a last resort. One tool that made this shift possible is Zoxide.
Zoxide: Smarter Navigation
Zoxide lets you jump to directories without typing full paths. It learns from your navigation history, ranks directories by frequency, and lets you teleport around your filesystem.
Instead of this ancient ritual:
1cd ~/development/active/skriuw
You just type:
1z sk
The more you use it, the smarter it becomes. It turns directory navigation into muscle memory.
How it works
For example, my Skriuw project lives at:
/home/remco-stoeten/development/active/skriuw
Launching a terminal usually drops me into:
/home/remco-stoeten/.config/dotfiles
Typing z web might be ambiguous if I have multiple web folders, but Zoxide supports multi-argument matching:
1z sk w
Boom. Zoxide narrows the match and jumps straight into /home/remco-stoeten/development/active/skriuw/apps/web. Even deeper paths like packages/db/migrations resolve instantly.
Aliases and Command Chaining
I am a big fan of aliases. Why type four commands when you can type one?
Here are a few of my favorites:
| Alias | Command | Description |
|---|---|---|
rmall | rm -rf node_modules .next | Nukes dependencies and build artifacts |
i | bun install | Fast installs with Bun |
b | bun build | Production build |
rs | next start | Preview the production build |
I often chain these together to clean, install, build, and start my app in one go:
1z .nl ; rmall ; i ; b ; rs
Here is what that looks like in action:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52β― pwd /home/remco-stoeten/.config/dotfiles dotfiles on master β― z .nl ; rmall ; i ; b ; rs π§Ή Starting cleanup... β Warning: Could not create backup: ENOTDIR: not a directory, mkdir '/home/remco-stoeten/.config/dotfiles/scripts/rmall/backups/latest' β Removed directory: node_modules β Removed directory: .next β Removed file: bun.lock ================================================== β Successfully removed: 3 ================================================== β¨ Cleanup completed successfully! [0.08ms] ".env.local", ".env" bun install v1.2.22 (6bafe260) warn: incorrect peer dependency "next@16.0.10" + @tanstack/react-query-devtools@5.91.1 .... 239 packages installed [554.00ms] $ next build β² Next.js 16.0.10 (Turbopack) - Environments: .env.local, .env Creating an optimized production build ... β Compiled successfully in 8.2s β Finished TypeScript in 7.7s β Collecting page data using 15 workers in 1833.0ms β Generating static pages using 15 workers (50/50) in 4.3s β Finalizing page optimization in 33.2ms Route (app) Revalidate Expire β β / β β /_not-found β β /api/github/activity 1m 1y β Ζ /api/github/commits ... all routes β (Static) prerendered as static content β (SSG) prerendered as static HTML (uses generateStaticParams) Ζ (Dynamic) server-rendered on demand $ next start β² Next.js 16.0.10 - Local: http://localhost:3000 - Network: http://192.168.1.238:3000 β Starting... β Ready in 385ms
Effectively what happened here without aliases is this:
1 2 3 4 5 6 7 8cd /home/$USER/development/active/skriuw cd apps/web rm -rf node_modules rm -rf .next *logic for checking if there is a dist or vite folder* bun install bun build bun run start
Removing all generated files and testing a fresh build.
Spellchecker
Another one which saves me quite some time is my spellchecker script. This lets me type spellcheck and on enter it prompts me to enter text, but passing text as an argument also works.
1 2 3 4 5 6 7 8 9 10spellcheck "i m writing a blog ppooost about my person al dvlopment enviorment" β Spellchecking with claude profile done β Done. Corrected text: I'm writing a blog post about my personal development environment. Result copied to clipboard. Applied 4 change(s).
Under the hood it checks whether you have one of the most common AI CLI's installed (that take prompts via argument) and runs it with the text you passed to it. In this case it uses Claude (with Z.ai) but could've also used Codex, Cursor or Gemini.
If curious you could test any of these scripts or the entire package by simply running my install script:
1 2 3 4git clone https://github.com/remcostoeten/dotfiles.git; cd dotfiles/setup; bun install; bun run dev
which prompts you with an interactive menu
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β β π Development Tools Setup β β β β Check installed tools and install missing ones β β β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ ββ Main Menu: β β 1. π Check Installation - Show what tools are installed β 2. π¦ Install Tools - Select categories and install β 3. βοΈ Toggle Dry Run - OFF β 4. β Exit - Quit the setup tool β
The scripts directory has loads more and are all standalone, so you can use them without the setup tool.