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.

Find a time to chat with me

Latest posts

// Make sure it won't end up in production!!! const TODO = "remove before production";
TypeScript

Building a CardDAV Server for Customer Contacts - When Next.js Gets in the Way

May 12, 2026

One 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.

Read more
blogposttimeli.shcarddavnext.js

Using next-intl in a Non-Next.js App - Taming the React Server Condition

May 12, 2026

Timeli.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

Read more
blogposttimeli.shnext.jsesbuildnext-intli18n

Contact me

Email

dmytro@bondarchuk.me
© 2026 Dmytro BondarchukCreated usingTimeli.sh