A story about building Clock-Tani — 9 free time-management tools in a single PWA, written with Next.js 16 App Router. Live at https://clock-tani.com
Why I Built This
I needed a pomodoro timer at work, but every option was either ad-heavy, app-only, or just ugly. Same story for world clocks, countdown timers, and interval timers. So I built them all into one place.
That place is now Clock-Tani — 9 small but useful tools, no ads in the core experience, no signup required.
What's Inside
- World Clock — 70+ cities, drag to reorder
- Timer / Pomodoro / Interval (Tabata, HIIT) / Multi-Timer
- Stopwatch with lap times
- Alarm Clock with 15 sounds
- Server Time (NTP) — for ticketing precision
- D-Day Counter — supports the Korean lunar calendar
Tech Stack
Next.js 16 (App Router) · React 19 · TypeScript · Tailwind CSS 4 · next-intl (KR/EN) · PWA (Service Worker) · @dnd-kit · korean-lunar-calendar
Five Implementation Highlights
1. App Router i18n with next-intl
I used the /[locale]/[tool] structure with localePrefix: 'always' so even /clock redirects to /ko/clock. This kept canonical URLs clean.
// src/middleware.ts
import createMiddleware from 'next-intl/middleware';
export default createMiddleware({
locales: ['ko', 'en'],
defaultLocale: 'ko',
localePrefix: 'always',
});
2. Making it Installable as a PWA
Just public/manifest.json + public/sw.js. Next.js doesn't need extra config. The service worker uses stale-while-revalidate so tools work offline.
<link rel="manifest" href="/manifest.json" />
3. Web Audio API for Reliable Alarm Sounds
The classic <audio> tag often gets blocked by mobile autoplay policies. After the user has clicked once, Web Audio API plays consistently:
const audioCtx = new AudioContext();
async function playSound(url: string) {
const res = await fetch(url);
const buffer = await audioCtx.decodeAudioData(await res.arrayBuffer());
const source = audioCtx.createBufferSource();
source.buffer = buffer;
source.connect(audioCtx.destination);
source.start();
}
4. Wake Lock API to Keep the Screen On
If the screen sleeps mid-timer, alarms get delayed. Wake Lock fixes it:
const wakeLock = await navigator.wakeLock.request('screen');
// release when timer ends
iOS Safari has supported it since 16.4, so this works almost everywhere now.
5. Auto-Generated OG Images for SEO
Each tool page has its own opengraph-image.tsx. Next.js generates the OG image at build time, which I reuse for Pinterest and social cards.
Lessons Learned
- There's a real demand for ad-free utility tools — organic traffic grows steadily.
- Almost no one installs the PWA. Most people just bookmark, so I now nudge "save to bookmarks" instead of "install app".
- i18n is much easier when designed up front than retrofitted.
Closing
It's funny — building time-management tools doesn't actually make me less distracted. But it has made me more thoughtful about how a small piece of software can quietly support someone's day.
Try it: clock-tani.com/en
Pomodoro: clock-tani.com/en/pomodoro
Originally posted on Medium and velog. Feedback welcome — especially from fellow indie makers building small useful things.
Top comments (0)