I built and launched TightTimeLog—a small offline-first PWA timer with bilingual UI and on-device logs—as a solo project. Along the way I hit AdSense rejection, underwhelming distribution experiments, and plenty of CSP surprises. This post bundles several shorter notes I published on my Hatena blog into one English piece for DEV.
Introduction
TightTimeLog was my first “ship it” personal project: PWA behavior, EN/JA UI, IndexedDB logs, and a focus on keeping data on the user’s device. Google AdSense did not approve the site (at least on the attempts I made), and marketing pushes did not move the needle as much as I hoped. Still, finishing deployment and seeing where things break was more valuable than the binary pass/fail. Below I split lessons into engineering, AI-assisted workflow, and distribution / monetization mindset.
1. Building with Cursor + Cloudflare Pages (and what hurt)
Why this stack
- Cursor: conversational coding, refactors, and research in one loop—huge for a solo dev’s throughput.
- Cloudflare Pages: static deploy + HTTPS + CDN with minimal ops—great default for a hobby site.
PWA and a little differentiation
I added haptic feedback on supported phones so the app feels slightly more “app-like” than a bare timer page. It’s a small touch, but it matches the goal of a pleasant mobile experience.
Design upfront
Using Figma early for icons and layout reduced late rework. For side projects, investing a bit in visuals before features explode pays off.
2. Practical prompting for i18n and “legal-ish” drafts (Cursor / Gemini)
i18n
The trick was to pin down library assumptions, output shape, and file layout in the prompt instead of saying “make it multilingual.” For example:
Externalize all user-visible strings; keep English and Japanese keys consistent with the existing
app.jsi18n object pattern; don’t leave hard-coded copy in HTML.
Fewer surprises, fewer round trips.
Privacy policy drafts
I listed facts only:
- Where data lives (e.g., IndexedDB, not uploaded)
- Third parties involved (hosting, contact form, ads if any)
- How users can reach me
Then I edited the draft manually. AI reduces blank-page time; it doesn’t replace responsible review.
3. Cloudflare Pages + CSP: when “it worked locally” dies in production
Production Content-Security-Policy headers blocked third-party scripts and iframes I hadn’t allowlisted—especially after adding AdSense-related domains. Fixing it meant evolving _headers until the live response headers matched what the browser needed.
Mental model
/*
Content-Security-Policy: default-src 'self'; script-src 'self' https://pagead2.googlesyndication.com ... ; frame-src https://googleads.g.doubleclick.net ... ;
Your exact directive will differ by which services you embed. I treated the browser console’s CSP violations as the source of truth for what to add.
Checklist I wish I had on day one
- Inspect
Content-Security-Policyon the real origin (notfile://). - Watch the console for CSP violations after each new script or embed.
- After changing
_headers, re-deploy and confirm headers changed (avoid stale SW caches during testing). - When using a service worker, bump cache versions or test in a clean profile if HTML responses look “stuck.”
4. AdSense rejection: my self-analysis (non-legal, non-official)
Google doesn’t owe us a precise reason, but my working theory:
- The product was optimized to be minimal UI copy, which can look like thin content to automated review.
- Policy also cares about where ads appear—navigation-only or low-value shells should not carry ads.
I responded by adding original explanatory copy on main screens and keeping ad tags off purely supplementary pages. Monetization-wise, I’m treating AdSense as one experiment, not the only path.
5. Posting on Qiita, X, and Product Hunt: distribution lessons
Rough outcomes
- X: posting cold from a fresh account barely moved traffic. I skipped community building (hashtags, consistent presence) and paid for it.
- Qiita & Product Hunt: dropping a link alone didn’t create sustained traffic.
Takeaways
- Context beats bare links—show up where your audience already discusses problems you solve.
- Even tiny reactions matter; they’re fuel when metrics are flat.
Next, I want to lean into Build in Public: share the process, not only the launch URL.
Closing
Shipping still beat not shipping. I’ll keep iterating on TightTimeLog and experiment with other projects too.
Japanese dev diary: Hatena Blog.
Personal learning log—not legal or financial advice. Verify policies and products for your own situation.













