Returning to the JS ecosystem (and its nice now?)
I felt a bit burned last time I seriously tried to get into modern full-stack typescript web frameworks. Part of this was also inexperience, but there is things that are objectively much better now:
Frameworks and tooling are better
… and more stable
i.e.
pnpm
exists so i can finally stop staring at a spinner for minutes every time i need to install packagesLLMs are good at writing React (specifically!)
Antropic claude is really good at fixing typescript linter issues
… and screwed up React hooks / prop drilling / etc.
… and npm package shenanigans
… and auto completing TS method signatures
Also I finally figured out how to do “select typescript version” in vscode so my LSP actually works (haha, oops)
A tiny project
So anyway I chugged a whole Pack of Kool-Aid and speedran making this super niche CRUD app by using Vercel, Clerk and Supabase.
The Vercel Postgres offer is weird, so I’m just cross cloud connecting to a supabase postgres url for now. This is not great, but this whole project is goofy anyway, so whatever.
React server components make sense to me. I did not care about performance considerations, but being able to put the S3 file uploading logic there to keep it simple was nice.
Should they have been named server side rendering components?
For a beginner, it is sometimes a bit confusing to have literally the same files be backend and frontend, but as a somebody who has written a few webapps in various ways, I understand the upsides of that, and the error messages are nice and clear.
Anyway, my app worked in a few hours and it was trivial to deploy including domain setup etc.
Clerk
Depending on the context, I am a third-party-service-hater on principle, and I have done my own auth a couple times, and it is not a huge deal. But it turns out that clerk is a really good offering, they made every single step for me as a dev nicer than using most consumer-facing apps. So credit where due!
NextJS build is slow
Configuring this helps: (thanks t3.gg)
next.config.js
typescript: {
ignoreBuildErrors: true,
},
eslint: {
ignoreDuringBuilds: true,
},
This speeds it up to “only” 1 minute. It also results in some breakages not being clearly errored anymore. So sometimes, I had to comment these out, before then running pnpm build
locally to get the errors I needed, so that I could then fix stuff I screwed up in the app router. That was the only time I felt a bit lost.
Unrelated, there was a some typescript Linter errors that took minutes of compilation (every time) to finally surface, but the routes worked fine without fixing them. (I am guessing that to a typescript expert, this is normal..?)
Import fixing using LLMs is slow
I really missed Golang’s immediate enforced automatic reformatting and package importing on saving a file. Fixing imports “by hand” feels surprisingly horrible!
Maybe I can get Vscode to do this for me somehow..?
settings.json
"editor.codeActionsOnSave": {
"source.organizeImports": "always",
"source.addMissingImports": "always"
}
EDIT: Ok yes, that finally works (and not break my CTRL+S this time). Works really well in fact! Nice
Web devs on windows....
… consider developing on a server.
My reasoning
- Having a proper shell. I have tried really hard to make the windows replacements work, but WSL is too slow, the microsoft drop ins for scp etc. are too incomplete, and using cygwin is also messy.
- Making WIP commits every time i change machines and then squashing them later seems a bit tedious and unnecessary
How
Prereq
- Set up an ssh config for your host. (If you have never done this, check the vscode section to have the extension do it for you, simplest.). In this example, the hosts config name is
myhost
. - Don’t have the servers’ ports open to the internet. We will forward them.
- Set up git authentication on the server
Neovim Version
- Use
ssh -L 1234:127.0.0.1:1234 myhost
(ssh tunnel) in a terminal other than the one where your neovim is running - http://localhost:1234 should work now.
- Adjust 1234 to be the port of your app running on server obviously. There is also an ssh flag to make it block, open a tunnel, but not open a remote shell, and stuff like that. It’s surprisingly nice.
- Happy coding!
Vscode/Cursor version
- Search for
ssh
in the extension store, there is versions for vscodium that work too - Install that stuff
- Click somewhere on the left pane to connect to remote hosts
- You may have to set up an ssh config for that host, punch in the login user and hostname once, in the future you can get here much faster
- Make sure your extensions are installed and enabled on the remote host, this is a bit annoying sometimes.
- Vscode should automatically forward ports that are opened on the server while it is running
- Happy coding!
"Project soup" is my Golang Rite of Passage
I have been a software dev for 10+ years, but learned Golang recently.
A big moment for me was when I started remixing parts of multiple open source projects into my own little project soup.
By the way, I’m being ethical here: Projects were MIT licensed, I am not selling anything I made this way, etc.
Anyway, the Project soup:
- Built from the ground up
- Know exactly how it works
- “Stolen” code integrated as modules
- Project setup did not become a mess
- Still compiles super fast
I think this is an example where many of the strengths of Golang come together in a beautiful way, and will try to shine some light on what made it possible for me to do this.
1 Go developers actually KISS
(Keep It Super Simple)
In Golang, there many things the language designers intentionally saved you from:
- Doing Object Oriented Programming with
class
es - Deciding where to handle
Exception
s - Extreme use of the type system
- Doing too much on one line of code
- Fiddling with code formatting
But!
There is also things that would be allowed, but Golang developers seem to avoid:
- Lots of abstractions piled on top of each other
- Extremely function flavored programming
- Bad use of libraries
- Overusing generics
The reasons for why Golang devs do not tend to do these things are probably complex.
I believe Golang attracts pragmatic developers that align with the goals and vibes of the language and prioritizing “getting shit done” over many other things, as well as KISSing.
Golang also changes the developers mindset to some degree by the errors and warnings the compiler emits. It does a good job of severly discouraging many practices without making them completely impossible. I would argue this is relevant, it certainly worked for me, and is a cool use of nudging. If ever there was a population that needed to be nudged, it is software developers.
Anyway, the absence of these practices is wonderful, and it is the most important piece for making the project soup possible, because reading other peoples code is not a cognitive nightmare! Hooray.
2 Nice Standard Library, Bro
I think the golang stdlib strikes a difficult balance very well. It embodies the KISS culture I was talking about while offering most of the things a golang user needs to do most of the time, and doing them well enough.
While I acknowledge various criticisms of it, developers did not feel the need to use a bunch of competing alternative standard libraries all over their code base (like in JS).
Notably neither the language itself, nor the golang stdlib has also not been fucked up by big changes to the language. I do not care how big the flaws in your language or stdlib are: If you make breaking changes, it is almost always a horrible trap. Just leave it alone.
Golang has been super careful to avoid this horrible trap. Thank you.
3 The Modules / Library system does not suck
The way imports/modules work is clear, debugging them is intuitive, and nothing major about them is messed up. If this sounds like table stakes to you, well, I wish you the best of luck with other languages.
Tips
I used some of the
replace github.com/foo/bar => ./bar
syntax in mygo.mod
files so that I could develop two separate projects in tandem. This is awesome.Take note of the
go vendor
command, which automates the annoying work of cloning package manager dependencies into your own source code.
The end!
I could ramble about cool Golang things more, but writing this is taking too much time.
PS
If you are trying out Golang, and annoyed about “unused variables” being a compiler error: It makes sense, don’t let it stop you from trying out this language :)
Poop Status Magazine
I think we need some sort of website that tracks the current enshittification status of popular companies.
Consider this example:
Status | Example Product | Reasoning | Recommendation |
---|---|---|---|
Growth | Cursor.sh | Everyone agrees product is pretty great. Newly released features actually add value. Occasional big bugs including security issues, but they get fixed quickly. | Enjoy the product while you can :) |
Bloat | Discord | Product is solid, but company is clearly flailing for a way to monetize. Bad decisions on the horizon. | Deprioritize the product when looking for solutions |
Enshittified | Dropbox | Used to be good, now is focusing on nagging end users constantly | Consider literally any rival products, there will be better ones! |
Stratified Shit | Microsoft Excel | Dinosaur offering shitty experience, mostly successful due to network effect | Make using the product somebody else’s problem |
Expanding on: Somebody else’s problem
Let’s take our example of Microsoft Excel.
- If you run your own small business, you can escape it with no problems. Use whatever you like instead.
- If you need to edit MS Excel sheets that people send you, you could try using onlyoffice. Do not tell them about it and see if anything major breaks. Play a bit dumb if needed.
- If you are in a big company and forced to use it, deal with it proactively. Find all the tricks to create a smooth running operation for yourself in the fossilized layers of “poo”. There is no shame in this!
Unshittifiable products
Nextcloud has its flaws, but it syncs my stuff just fine. For almost ten years. Similar to Dropbox, they released a flurry of enterprise features to help sell it to governments agencies and businesses.
But you know what? They did not start nagging their end users. Their incentives are not aligned in this way, because they are not an incredibly overvalued SV company that suddenly feels the need to squeeze their 300M users for all they are worth “because oops I guess we need start making a lot of money right now”.
By the way, I would like to praise any government agency buying services from Nextcloud, because they are following the crucial rule of not buying closed source products as a government agency. Such products will inevitably turn into an enterprise-grade leech that sucks up too much tax money (due to game theory reasons).
And yes, open source to the rescue: If Nextcloud tried to do that, somebody would fork the project and release a guide on how to deploy NextNextCloud, and the bosses of the affected users could hire some consultants to replace their Nextclouds, and everything would be completely fine.
Summary
Microsoft bad, Open Source good
Official, paid Bootstrap themes are not nice
Many of us have used the bootstrap CSS framework over the years. It’s not the hotness anymore, but my management decided we should continue to use it, went on that website and bought a theme for ~100$.
Next, there was business a requirement for changing the primary color of the theme. The theme does not expose CSS variables for tweaking the colors (or anything useful). This feels wrong to me.
I proceeded by replacing the color values that I need to customize in _variables.scss
, then tried to build the theme using the build process they had documented and the gulp script they had shipped in the repo. But it sucked a lot:
- Install was a pain, correct NPM version not documented, install did not work, had to coax the install to the point of basically rebuilding it from scratch.
- Grunt scrip theoretically offers live reload, but not feasible, because whatever garbage JS libraries they used are leaking gigabytes of memory and rebuilding from scratch, which takes absolutely forever
- A bunch of Docs/JS crap is included in the gulp script, there is no task to just build the CSS, I had to do that myself.
- Grunt script is shoddily made, saves two copies of everything it builds, does not clean properly, resulting in deleted stuff ghosting around in the final build
So this was not great. The way forward is obviously adding CSS variable support myself, so that we can get rid of this build process forever. So I tried injecting CSS variables as color values, but they are using SCSS
color transform functions that make this fail.
As soon as I finally finish this hassle at work, I will publish the diffs for doing transform on top of a themed bootstrap 4.X build, in case I ever need to do this again in my next job, and in case it helps somebody.
.NET+HTMX can be a decent experience
Since this is a simple app, I took the opportunity to find a nice way to use HTMX with .NET.
I sent my HTMX requests to a proper .NET API controller that then renders a hierarchy of ViewComponents which act as reusable website components. The required data is mapped at render time by the caller.
If you are interested in this setup, Source code is available here.
Credits also to Jetbrains for their tutorial series: HTMX for ASP.NET Core Developers, a good starting point for anyone interested. Their rendering setup differs from mine.
For the real-time autocomplete, I turned away from HTMX, because while server round trips for the main input boxes of an app are feasible, the resulting experience would not be good.
I wanted people to be able to play this game on the train etc. without any kind of lag. So I wrote some vanilla Javascript that fetches the country list and displays the autocomplete box.
Additional thoughts
HTMX should be fine in medium-sized because you can use your normal backend abstractions to keep the code clean. Just make functions that return HTML strings.
It is a bit unusual, and some people might hate it, but I did this successfully in a different project and it worked out fine!
What you do need is some sort of click-through-everything, end-to-end testing, because nothing is compiled.