<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: spoon yang</title>
    <description>The latest articles on DEV Community by spoon yang (@spoon_yang).</description>
    <link>https://dev.to/spoon_yang</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3866860%2F85051b59-c324-489a-bf2a-655446816302.png</url>
      <title>DEV Community: spoon yang</title>
      <link>https://dev.to/spoon_yang</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/spoon_yang"/>
    <language>en</language>
    <item>
      <title>How I Built an AI Tattoo Generator with Next.js, Cloudflare, Google OAuth, and PayPal</title>
      <dc:creator>spoon yang</dc:creator>
      <pubDate>Thu, 09 Apr 2026 07:50:48 +0000</pubDate>
      <link>https://dev.to/spoon_yang/how-i-built-an-ai-tattoo-generator-with-nextjs-cloudflare-google-oauth-and-paypal-16hj</link>
      <guid>https://dev.to/spoon_yang/how-i-built-an-ai-tattoo-generator-with-nextjs-cloudflare-google-oauth-and-paypal-16hj</guid>
      <description>&lt;h1&gt;
  
  
  How I Built an AI Tattoo Generator with Next.js, Cloudflare, Google OAuth, and PayPal
&lt;/h1&gt;

&lt;p&gt;I recently shipped &lt;a href="https://bodyink.art/" rel="noopener noreferrer"&gt;&lt;strong&gt;BodyInk.art&lt;/strong&gt;&lt;/a&gt;, an AI tattoo generator that helps users explore tattoo concepts before going to a studio.&lt;/p&gt;

&lt;p&gt;In this post, I want to share the engineering side: what worked, what broke in production, and what I changed to make the app stable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Product Goal
&lt;/h2&gt;

&lt;p&gt;Tattoo ideation is often slow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;users collect references from many places&lt;/li&gt;
&lt;li&gt;style direction is unclear early on&lt;/li&gt;
&lt;li&gt;multiple revision rounds happen before a final direction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanted a workflow where users can type an idea, generate concepts quickly, and download results for discussion with tattoo artists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stack Choice
&lt;/h2&gt;

&lt;p&gt;I used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Next.js App Router&lt;/strong&gt; for pages and API routes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare Pages&lt;/strong&gt; for deployment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google OAuth (NextAuth)&lt;/strong&gt; for sign-in&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PayPal&lt;/strong&gt; for pay-per-generation&lt;/li&gt;
&lt;li&gt;an AI image backend for tattoo image generation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This stack gave me fast iteration and global delivery, but it also exposed some runtime differences I had to handle.&lt;/p&gt;

&lt;h2&gt;
  
  
  The First Real Production Bug
&lt;/h2&gt;

&lt;p&gt;After deployment, image generation failed with:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Cannot read properties of null (reading 'has')&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;At first it looked like a random null bug, but the root cause was runtime incompatibility. A dependency path assumed Node-like behavior, while the deployed environment executed in an Edge-like context.&lt;/p&gt;

&lt;h3&gt;
  
  
  What fixed it
&lt;/h3&gt;

&lt;p&gt;I replaced the problematic SDK-dependent path with a simpler fetch-based implementation and removed Node-only assumptions from the generation flow.&lt;/p&gt;

&lt;p&gt;Result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no more random null access in the hot path&lt;/li&gt;
&lt;li&gt;easier debugging because request/response behavior became explicit&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Base64 and Edge Runtime Gotchas
&lt;/h2&gt;

&lt;p&gt;Another source of 500 errors was image encoding. The naive conversion strategy caused performance and reliability issues under constrained runtime conditions.&lt;/p&gt;

&lt;p&gt;I switched to &lt;strong&gt;chunked Uint8Array processing&lt;/strong&gt; instead of relying on heavier Node-oriented patterns. This reduced pressure on runtime limits and made generation responses stable.&lt;/p&gt;

&lt;p&gt;If you process image buffers on serverless/edge platforms, don’t assume desktop Node behavior will hold.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Google OAuth with NextAuth
&lt;/h2&gt;

&lt;p&gt;I integrated Google sign-in using NextAuth with callback route:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/api/auth/callback/google&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Implementation notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keep OAuth credentials in environment variables&lt;/li&gt;
&lt;li&gt;define the correct &lt;code&gt;NEXTAUTH_URL&lt;/code&gt; per environment&lt;/li&gt;
&lt;li&gt;verify provider callback settings match your production domain exactly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The biggest practical issue is usually config mismatch, not code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding PayPal: Sandbox First, Then Live
&lt;/h2&gt;

&lt;p&gt;The business rule is simple: &lt;strong&gt;$1 per generation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I implemented two endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create order&lt;/li&gt;
&lt;li&gt;capture payment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And I kept env-driven mode switching so I could test safely in Sandbox and then move to Live.&lt;/p&gt;

&lt;p&gt;Important details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;lock amount/currency on the server side&lt;/li&gt;
&lt;li&gt;never trust client-only payment state&lt;/li&gt;
&lt;li&gt;return clear failure messages to avoid silent checkout errors&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  SEO and Analytics
&lt;/h2&gt;

&lt;p&gt;After core flows worked, I optimized discoverability:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;improved page-level metadata&lt;/li&gt;
&lt;li&gt;added sitemap and robots&lt;/li&gt;
&lt;li&gt;integrated Google Analytics globally&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For analytics in Next.js App Router, a framework-aligned integration keeps scripts cleaner and avoids layout-level duplication.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Runtime differences matter more than local success.&lt;/li&gt;
&lt;li&gt;Keep payment logic server-authoritative.&lt;/li&gt;
&lt;li&gt;Integrations fail more from misconfiguration than code defects.&lt;/li&gt;
&lt;li&gt;Production debugging gets easier when dependencies are minimal in critical paths.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What I’d Improve Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;better prompt guidance for first-time users&lt;/li&gt;
&lt;li&gt;stronger style consistency controls&lt;/li&gt;
&lt;li&gt;smarter retry/fallback strategy for generation failures&lt;/li&gt;
&lt;li&gt;clearer pricing and credit UX&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Shipping AI features is not just model quality. It is mostly engineering reliability:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;predictable runtime behavior&lt;/li&gt;
&lt;li&gt;robust payment flow&lt;/li&gt;
&lt;li&gt;traceable errors&lt;/li&gt;
&lt;li&gt;fast iteration loop&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re building a similar AI product on Next.js + Cloudflare, I hope this saves you a few painful nights.&lt;/p&gt;

&lt;p&gt;If you want, I can share a follow-up post with concrete route structure, env templates, and a deployment checklist.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>nextjs</category>
      <category>showdev</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
