DEV Community

Katsutoshi Imanishi
Katsutoshi Imanishi

Posted on

What I Learned Shipping TightTimeLog: AI Workflow, CSP, AdSense, and Distribution

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.js i18n 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 ... ;
Enter fullscreen mode Exit fullscreen mode

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

  1. Inspect Content-Security-Policy on the real origin (not file://).
  2. Watch the console for CSP violations after each new script or embed.
  3. After changing _headers, re-deploy and confirm headers changed (avoid stale SW caches during testing).
  4. 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.

Top comments (0)