Deep dives on software engineering, architecture, and the real-world decisions
This blog runs on software I wrote. Here's everything I learned doing it.
Latest posts
// Make sure it won't end up in production!!!
const TODO = "remove before production";Making Plate.js Static Render Fast - From 30 Seconds to 300ms
May 13, 2026The blog on bondarchuk.me runs on timeli.sh - specifically on its blog app, which stores posts as Plate.js JSON and renders them server-side on public pages using PlateStatic, the read-only renderer that ships with @udecode/plate. For short posts this works fine. For longer posts - like the payment integration write-up with multiple code blocks, tables, and several thousand words - the initial page load was taking around 28-32 seconds. During that time, one CPU core was pegged and the Node.js process was blocked from serving anything else.
This post is the story of two attempts to fix it. The first brought the time down to around 7.5-8.5 seconds with a visual shimmer to mask the wait. The second brought it down to around 280-320ms and removed the shimmer entirely.
What PlateStatic actually does, and why it's slow for large documents
PlateStatic renders a Plate document by walking the node tree and dispatching each node to its registered static element component - a React component per node type (paragraph, heading, code block, link, bold leaf, etc.). For a long post with hundreds of nodes, that means a very large React component tree: every paragraph, every heading, every code line, every text span with a mark becomes a React element.
Building a CardDAV Server for Customer Contacts - When Next.js Gets in the Way
May 12, 2026One of the features I wanted to add to timeli.sh is the ability for service businesses to sync their customer list directly to their phone's contacts app. If a client calls, the business owner should see "Sarah - Haircut Tuesday 3pm" rather than an unknown number. The standard protocol for this is CardDAV - the same protocol Apple Contacts, Google Contacts, and most third-party apps use to sync address books.
The implementation ended up being two distinct pieces: a connected app in the app store that organizations install and configure, and a separate lightweight Node.js server that actually speaks CardDAV. The reason those are separate - and why the CardDAV server is not just a Next.js API route - comes down to one hard limitation in Next.js that I hit immediately.
Why Next.js can't host a CardDAV server
Next.js App Router Route Handlers support a fixed set of HTTP methods: GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS. You export a function named after the method and Next.js routes requests to it.
Using next-intl in a Non-Next.js App - Taming the React Server Condition
May 12, 2026Timeli.sh is a monorepo. The main customer-facing app and the admin dashboard are both Next.js, so using next-intl there is straightforward - install the package, wire up getRequestConfig, add the plugin to next.config.js, done. But there is a third app in the repo: job-processor, a Node.js background worker that runs appointment reminders, SMS notifications, and other async tasks. It is built with esbuild and has no Next.js anywhere near it.
The job-processor sends emails and SMS messages that contain translated strings. Those strings come from the same shared @timelish/i18n package that the Next.js apps use, which is built on top of next-intl. So I needed next-intl's getTranslations to work inside a plain Node.js process, bundled by esbuild, with no React renderer, no request context, and no next/headers available at runtime.
This post is about the three things that stood between me and a working build.
Why next-intl breaks outside Next.js
Adding Booking Deposits to a Multi-Tenant SaaS with PayPal, Square, and Stripe - Including Apple Pay
May 9, 2026Timeli.sh lets service businesses collect deposits, full prepayments, and in-person payments at the time of booking. Adding that feature meant integrating three payment processors - PayPal, Square, and Stripe - behind a single abstraction so each organization could connect whichever processor they already use. I also needed Apple Pay to work through both PayPal and Square, which turned out to be the most involved part of the whole thing.
This post covers the architecture, the per-processor specifics, and a detailed breakdown of how Apple Pay was added to each one.
The architecture: a pluggable payment processor interface
Timeli.sh uses an app store model where every external integration - email, SMS, calendar, payments - is a connected app. Each app implements a typed interface, and the booking system talks to whatever payment app the organization has installed.
How I Integrated Polar.sh Payments Into My SaaS With Better Auth - What the Docs Don't Tell You
May 8, 2026When I added subscription billing to timeli.sh, I chose Polar.sh as the payment layer and better-auth as the auth framework. On paper, both are excellent. In practice, there was one fundamental mismatch between how Polar + better-auth expect things to work and how timeli.sh is actually structured - and I spent more time fighting it than building the feature.
This post is the write-up I wish had existed before I started.
The problem: every guide assumes subscriptions belong to a user
Timeli.sh is a multi-tenant SaaS. A user creates an organization, and that organization is what subscribes. Multiple users can belong to an organization, so it would be wrong for the subscription to live on any individual user. The organization either has an active subscription or it doesn't - regardless of who's logged in.
How I got Caddy's on-demand TLS working inside Coolify
April 13, 2026Timelish needs to provision SSL certificates automatically for both subdomains and custom domains. Every business that connects its own domain to its booking page needs HTTPS to just work - no manual cert setup, no waiting, no friction. They type in mybusiness.com, and it works. The same also applies if they use their default subdomain, like mybusiness.timeli.sh .
The feature that makes this possible is Caddy's on_demand_tls. And getting it to play nicely with Coolify took a few hours of digging that I want to save you.
What on-demand TLS actually is
Most reverse proxies handle TLS by issuing certificates at startup - you define your domains upfront, the proxy fetches certs from Let's Encrypt, done. That works great when you know your domains ahead of time.
This blog runs on software I built. Here's why that's both exciting and terrifying.
April 10, 2026Every developer has, at some point, thought: I should build my own version of that.
For me, that thought came while I was trying to set up appointment scheduling for my beautiful wife - a nail artist who was drowning in DMs, trying to manually coordinate bookings, no-shows, and reminders every single day. I looked at the existing tools. They were either too complex, too expensive, or too ugly. So I did what developers do.
I built my own.
That project became Timelish — an all-in-one appointment scheduling platform for small businesses. And this blog you're reading right now? It's built on top of it. Which means every post I publish here is, in a small way, a live demo of the very product I'm writing about.
That's the thing I want to explore in this space: what it actually looks like to build a SaaS from scratch, make real engineering decisions under real constraints, and ship something people depend on.