AI
50 AI Prompts for Building a SaaS App with Next.js and Stripe
A developer-compiled set of 50 prompts for building a production-ready SaaS with Next.js App Router, Stripe subscriptions, and Prisma — covering project setup, auth, billing, webhooks, dashboards, and deployment.
Advertisement
How to Get the Most Out of These Prompts
Most AI prompt lists fail because they are too vague. "Build me a Stripe integration" gives you a skeleton that does not fit your actual project. Every prompt here is written with enough context that the AI understands what file it is working in, what pattern it should follow, and what the output needs to connect to. That specificity is what separates useful output from something you rewrite from scratch.
These prompts assume you are using Next.js 14 with App Router, Stripe for subscriptions, TypeScript, Tailwind CSS, and Prisma as your ORM. They are designed to be used sequentially on a new project or pulled individually when you hit a specific problem in an existing one. The order matters — especially in the early sections — because later prompts reference files created by earlier ones.
One habit that dramatically improves results: always paste in the relevant file you are currently working on before running the prompt. A prompt asking you to "add webhook handling to your route" produces far better output when the AI can see your existing route structure. Most of the failures with AI-generated code come from the model guessing at context that you could have provided directly.
- Paste your existing file before running prompts that modify it
- Run setup and architecture prompts first — later ones depend on those files
- When output looks wrong, add "following the pattern in [filename]" to the prompt
- For complex prompts, split them — one for the API route, one for the component
Project Setup and Architecture (Prompts 1–8)
These eight prompts handle decisions that are painful to undo once a project grows — folder structure, environment validation, database connection handling, and middleware setup. Getting these right in the first week prevents most of the technical debt that accumulates by week six.
Prompt 1: "Set up a Next.js 14 App Router project with TypeScript, Tailwind CSS, and Prisma. Create a prisma/schema.prisma file with a User model that includes id, email, name, createdAt, updatedAt, stripeCustomerId (nullable string), subscriptionStatus (enum: FREE, TRIALING, PRO, ENTERPRISE, CANCELED), and subscriptionId (nullable string). Show the commands to run prisma generate and prisma migrate dev and explain what each does."
Prompt 2: "Create a lib/db.ts file that exports a singleton Prisma client for a Next.js App Router project. Handle the global caching pattern correctly so hot reload in development does not create multiple Prisma connections. Add a short comment explaining why the global assignment is necessary in dev." — Prompt 3: "Set up environment variable validation using zod in lib/env.ts. Validate these server-side vars: DATABASE_URL, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, NEXTAUTH_SECRET. Validate these client-side vars: NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY, NEXT_PUBLIC_APP_URL. Throw a descriptive error on startup if any required var is missing."
- Prompt 4: "Create a middleware.ts at the project root using NextAuth getToken to protect all /dashboard routes. Redirect unauthenticated users to /login. Allow public access to /, /login, /register, /pricing, /api/webhooks/stripe, and all routes under /api/public."
- Prompt 5: "Set up a Next.js App Router folder structure for a SaaS product. Show me the layout for: app/(auth) for login and register, app/(dashboard) for protected app routes, app/(marketing) for public pages, app/api for route handlers, components split into ui/dashboard/shared folders, and a lib folder for utilities, hooks, and Stripe helpers. Explain what belongs in each location."
- Prompt 6: "Create a types/index.ts file with TypeScript interfaces for User (matching the Prisma model), Subscription (with plan, status, currentPeriodEnd as Date, and cancelAtPeriodEnd as boolean), and ApiResponse<T> (a generic wrapper with data, error, and status fields)."
- Prompt 7: "Write a lib/utils.ts with a cn() function using clsx and tailwind-merge for merging Tailwind class names, a formatCurrency(amountInCents, currency) function using Intl.NumberFormat, and a formatDate(date) function that returns a readable string like May 28, 2026. Export all three as named exports."
- Prompt 8: "Create app/error.tsx and app/global-error.tsx error boundary files for Next.js App Router. The error boundary should show a user-friendly message with a retry button. In development, log the error and stack trace to console. In production, do not expose raw error details. Both files should be client components."
Authentication with NextAuth (Prompts 9–15)
These prompts use NextAuth v4 with the Prisma adapter. If you are using Clerk, skip to prompt 13 which covers the dashboard layout — the rest of the auth flow is handled by Clerk's components directly. The prompts here assume credentials-based auth plus an optional OAuth provider, which covers most SaaS use cases.
Prompt 9: "Set up NextAuth v4 in a Next.js App Router project. Create app/api/auth/[...nextauth]/route.ts with a Credentials provider that checks email and password against the Prisma database using bcrypt.compare. Add the Prisma adapter. In the callbacks, attach userId and subscriptionStatus to the JWT token and session object so they are accessible via useSession and getServerSession." — Prompt 10: "Create a POST handler at app/api/auth/register/route.ts. Accept email, name, and password. Validate inputs with zod. Hash the password with bcrypt using 10 rounds. Check if the email already exists first — return 409 if it does. Create the user, then return 201 with the user id. Never return the hashed password."
Prompt 11: "Write a getServerUser() helper in lib/auth.ts that calls getServerSession, extracts the user from the session, and returns a typed CurrentUser object or null. Show how to call it in a Server Component and in an API route handler." — Prompt 12: "Create a useCurrentUser hook in hooks/use-current-user.ts. It should use useSession from next-auth/react and return the typed user with id, email, name, subscriptionStatus, and an isLoading boolean. Export a CurrentUser type from the same file."
- Prompt 13: "Build a login page at app/(auth)/login/page.tsx. Form fields: email and password. On submit call signIn('credentials') and handle the error response. Show an inline error message if credentials are wrong. Redirect to /dashboard on success. Simple Tailwind layout — centered card, logo above, link to register below."
- Prompt 14: "Create a withAuth utility in lib/api-helpers.ts that wraps a Next.js App Router route handler. It should call getServerSession, return a 401 JSON response if unauthenticated, and pass the session user to the wrapped handler as a parameter so the handler does not need to call getServerSession again."
- Prompt 15: "Create an AuthProvider component that wraps children with SessionProvider from next-auth/react. Show me the correct place to add it in the root app/layout.tsx for Next.js App Router, and explain why it needs to be a client component even though the layout itself might be a server component."
Stripe Products and Checkout (Prompts 16–22)
These prompts assume you have already created your products and prices in the Stripe dashboard. They focus on fetching that data into your app, building the pricing UI, and creating checkout sessions. The prompts use the Stripe Node.js SDK v14 syntax.
Prompt 16: "Create lib/stripe.ts that initializes a Stripe instance using STRIPE_SECRET_KEY with apiVersion '2023-10-16'. Export the instance. Also export a PLANS constant that maps FREE, PRO, and ENTERPRISE to their Stripe price IDs using environment variables — never hardcode price IDs in application logic." — Prompt 17: "Build a pricing page at app/(marketing)/pricing/page.tsx. Fetch active prices from Stripe server-side using the Stripe SDK. Display three plan cards: Free (no charge), Pro (monthly), and Enterprise (monthly). Each card shows the plan name, price, a bullet list of features (you can hardcode the features), and a CTA button. Highlight the Pro card visually."
Prompt 18: "Create app/api/stripe/create-checkout-session/route.ts. Accept POST with a priceId body parameter. Get the current user from the session with withAuth. If the user does not have a stripeCustomerId, call stripe.customers.create with their email and name, then save the returned customer id to the database. Create a checkout session with the customer id, priceId, success_url (/success?session_id={CHECKOUT_SESSION_ID}), and cancel_url (/pricing). Return the session URL." — Prompt 19: "Write a CheckoutButton client component that takes a priceId and a currentPlan prop. On click, POST to /api/stripe/create-checkout-session and redirect to the returned URL. Show a spinner while loading. If currentPlan matches the plan for this button, show a disabled 'Current Plan' button instead."
- Prompt 20: "Add a 14-day free trial to the Pro plan checkout session. Use subscription_data: { trial_period_days: 14 } in the session creation. Update the success page to detect when the session includes a trial and show a message like 'Your trial starts today — you will not be charged until [date].'"
- Prompt 21: "Create a success page at app/(marketing)/success/page.tsx. Read the session_id query param. Fetch the Stripe session server-side to confirm the checkout completed. Display the plan name, the amount charged (or trial message), and a button to go to the dashboard. If session_id is missing or invalid, redirect to /pricing."
- Prompt 22: "Add coupon code support to the checkout session. Accept an optional coupon string in the create-checkout-session request body. Before creating the session, call stripe.coupons.retrieve to verify the coupon is valid and not expired. If invalid, return 400 with the message 'That discount code is not valid.' If valid, pass discounts: [{ coupon }] to the session."
Stripe Webhooks (Prompts 23–29)
Webhooks are where most Stripe integrations go wrong. The signature verification step catches a lot of developers — you must read the raw request body before parsing it, otherwise the signature check fails every time. These prompts get the handler structure right first, then layer in the event handling.
Prompt 23: "Create app/api/webhooks/stripe/route.ts. Verify the Stripe webhook signature using stripe.webhooks.constructEvent. Read the raw body with await request.text() before any parsing — do not use request.json(). Pass the raw string body, the Stripe-Signature header, and STRIPE_WEBHOOK_SECRET to constructEvent. Return 400 if verification fails. Return 200 for unhandled event types after logging the event type so you can track what Stripe sends." — Prompt 24: "Add a handler for the checkout.session.completed event in the webhook route. Extract the customer id from the session object. Query the database for a user with that stripeCustomerId. If found, update their subscriptionStatus to PRO and store the subscriptionId from session.subscription. If no user is found, log a warning — this usually means the customer record was created outside your system."
Prompt 25: "Handle the customer.subscription.updated event. Extract subscription status, current_period_end, and cancel_at_period_end. Map Stripe status values to your internal enum: active → PRO, trialing → TRIALING, past_due → PRO (keep access, handle separately), canceled → CANCELED. Update the matching user record. Log any status value you do not recognise so you can handle it later." — Prompt 26: "Handle customer.subscription.deleted in the webhook. Find the user by stripeCustomerId. Set subscriptionStatus to FREE and clear the subscriptionId field. Then call your sendEmail utility to send a cancellation confirmation to the user. Return 200 after completing both operations."
- Prompt 27: "Handle invoice.payment_failed. Find the user by customer id. Increment a paymentFailureCount field on the user record. If paymentFailureCount reaches 3, set a boolean flag paymentOnHold: true. Send a payment failure email on every failed attempt. Include the invoice amount and a link to the Stripe billing portal in the email."
- Prompt 28: "Write a syncStripeSubscription(userId: string) utility in lib/stripe-sync.ts. Fetch the user from the database. Use their subscriptionId to call stripe.subscriptions.retrieve. Update the database with the current status, currentPeriodEnd, and cancelAtPeriodEnd from Stripe. Return the updated user. Use this for manual resyncs after webhook failures or support tickets."
- Prompt 29: "Show me the exact Stripe CLI commands to install the CLI on Ubuntu and Windows, authenticate, and forward webhooks to localhost:3000/api/webhooks/stripe. Explain that the webhook signing secret shown by the CLI (starts with whsec_) is different from the production secret in the Stripe dashboard, and show how to handle both in the environment config."
Dashboard and User Portal (Prompts 30–37)
Once billing works, the dashboard is where most of the remaining product lives. These prompts cover the layout shell, subscription state UI, settings, and account management flows.
Prompt 30: "Create app/(dashboard)/layout.tsx. Include a sidebar with links to /dashboard, /dashboard/billing, /dashboard/settings, and /dashboard/usage. Fetch the current user server-side using getServerUser() and pass their name and subscriptionStatus to a DashboardHeader client component. The header should show the user name, a PlanBadge component, and a sign out button." — Prompt 31: "Build app/(dashboard)/page.tsx as the dashboard home. Show a welcome message using the user's name. Add a stats row with three cards: Links Created, Total Clicks, and Active Sequences. Use placeholder numbers for now — we will wire real data later. Add a quick actions section with two buttons: Create Link and View Analytics."
Prompt 32: "Create a PlanBadge component that takes a subscriptionStatus prop. FREE renders a gray badge. TRIALING renders a yellow badge with the label 'Trial'. PRO renders a blue badge. ENTERPRISE renders a purple badge. CANCELED renders a red badge. Keep it small — intended to sit next to a username or page heading." — Prompt 33: "Build a UsageMeter component. Props: used (number), limit (number), label (string). Render a progress bar. The bar is blue below 80% usage, yellow from 80–99%, and red at 100%. Show the fraction (e.g. 43 / 100 links) below the bar. If limit is null or undefined, show 'Unlimited' instead of the fraction."
- Prompt 34: "Create app/api/user/profile/route.ts with GET and PATCH. GET returns the current user profile (id, name, email, subscriptionStatus). PATCH validates name (non-empty string, max 80 chars) with zod, updates the database, and returns the updated profile. Return 400 with field-level validation errors if input is invalid."
- Prompt 35: "Build app/(dashboard)/settings/page.tsx with two sections. Profile section: an editable name field that calls the PATCH /api/user/profile route on submit. Show a success toast on save. Danger Zone section: a Delete Account button that opens a confirmation dialog. The dialog requires the user to type their exact email address before the delete button activates."
- Prompt 36: "Create a DeleteAccountDialog client component with a controlled input. The confirm button stays disabled until the typed value matches the user's email exactly. On confirm, call DELETE /api/user/account. On success, call signOut() and redirect to /. On error, show the error message returned by the API inside the dialog."
- Prompt 37: "Create DELETE /api/user/account. Verify the session. Cancel the user's active Stripe subscription first using stripe.subscriptions.cancel (pass cancel_at_period_end: false for immediate cancellation). Then delete the user from the database. If the Stripe cancellation fails, do not delete the account — return the error instead. Log the deletion with user id and timestamp for audit purposes."
Billing Portal and Upgrades (Prompts 38–43)
The Stripe customer portal handles most billing self-service — plan changes, payment method updates, invoice history, and cancellation. These prompts wire the portal into your app and add the flows that require custom handling: upgrade confirmations, reactivation, and trial conversion.
Prompt 38: "Create app/api/stripe/create-portal-session/route.ts. Get the current user from the session. If the user has no stripeCustomerId, return 400 with the message 'No billing account found — please contact support.' Otherwise call stripe.billingPortal.sessions.create with their customerId and a return_url of NEXT_PUBLIC_APP_URL + '/dashboard/billing'. Return the portal URL." — Prompt 39: "Build app/(dashboard)/billing/page.tsx. Show the current plan name and status. If subscribed, show the next billing date and whether auto-renewal is on. Add a Manage Billing button that calls /api/stripe/create-portal-session and redirects to the portal URL. If the user is on the FREE plan, show a message pointing them to /pricing."
Prompt 40: "Create a SubscriptionBanner component that renders at the top of all dashboard pages when action is needed. Show a yellow banner when subscriptionStatus is TRIALING and trial ends within 3 days — include the exact end date and a link to add a payment method. Show a red banner when status is CANCELED — include a reactivate link. Show an orange banner when status is past_due — include a link to the billing portal. Return null for all other statuses." — Prompt 41: "Build a reactivation flow for users who canceled but the subscription is still active until period end. In the billing page, if cancelAtPeriodEnd is true, show a Reactivate Subscription button. The button calls a POST /api/stripe/reactivate route that calls stripe.subscriptions.update with cancel_at_period_end: false. Show a success toast after reactivation and refetch the subscription state."
- Prompt 42: "Create a plan upgrade flow for existing subscribers. When a PRO user clicks Upgrade to Enterprise, instead of redirecting to Stripe checkout, call stripe.subscriptions.update with the new price ID and proration_behavior: 'create_prorations'. First retrieve the subscription to get the subscription item id — you need it for the items array in the update call. Show the prorated charge estimate in a confirmation modal before applying the change."
- Prompt 43: "Add a trial-to-paid conversion prompt. When a TRIALING user visits the dashboard and has fewer than 5 days left, show a modal (not a banner) with the trial end date, their selected plan, and a button to Add Payment Method that opens the Stripe billing portal. Store a dismissedTrialModal: true key in localStorage so the modal only shows once per session."
Email Notifications (Prompts 44–47)
These prompts use Resend for transactional email with React Email templates. The same patterns work with other providers — swap the SDK call in lib/email.ts and the rest stays the same.
Prompt 44: "Set up Resend for transactional email. Create lib/email.ts that exports a sendEmail(to: string, subject: string, reactComponent: React.ReactElement) function using the Resend SDK. Add RESEND_API_KEY to the env validation. Send from noreply@yourdomain.com with reply-to support@yourdomain.com. Log errors but do not throw — failed emails should not crash the request that triggered them." — Prompt 45: "Create a WelcomeEmail React component in emails/welcome-email.tsx using @react-email/components. It should include: the user's name, a three-step getting started checklist with links to relevant dashboard pages, and a CTA button to the dashboard. Keep the design minimal — white background, dark text, one brand-color button. Send this from the registration route after the user record is created."
Prompt 46: "Create a PaymentReceiptEmail component. It should display the plan name, the formatted amount charged, the billing period dates, and a link to the billing dashboard. Render and send this from the checkout.session.completed webhook handler. Get the customer email from the Stripe session object directly — do not assume the database email is current." — Prompt 47: "Write a TrialReminderEmail component and a sendTrialReminders() function. The function should query users where subscriptionStatus = TRIALING and the trial ends within the next 72 hours. For each user, send the reminder email with their name, the trial end date, and a link to the billing portal to add a payment method. Log how many reminders were sent. Design this to be called from a cron job or a daily scheduled function."
Deployment and Going Live (Prompts 48–50)
These last three prompts cover the production deployment decisions that are easy to skip during development and expensive to fix after you have real users. Worth running before you share the app publicly.
Prompt 48: "Create a production deployment checklist for a Next.js + Stripe + Prisma SaaS on Vercel. Cover: which environment variables to set and where to get each value, how to register the Stripe webhook endpoint in the Stripe dashboard and which specific events to subscribe to, the correct build command override for Prisma on Vercel (prisma generate needs to run before next build), and how to confirm the first webhook delivery worked after deploy." — Prompt 49: "Create a GitHub Actions workflow file at .github/workflows/deploy.yml that triggers on push to main. Steps: check out the repo, install Node 20, run npm ci, run npx tsc --noEmit for type checking, run tests if they exist (do not fail if no test script), then deploy to Vercel using the vercel CLI with VERCEL_TOKEN, VERCEL_ORG_ID, and VERCEL_PROJECT_ID stored as repository secrets."
Prompt 50: "Explain the difference between prisma migrate dev and prisma migrate deploy, and show me how to run migrate deploy automatically before each Vercel production deployment. Show the Vercel build command configuration (in vercel.json or the dashboard) that runs prisma generate && prisma migrate deploy && next build. Explain why you must never run migrate dev in production and what can go wrong if you do."
- Run prisma migrate deploy, not migrate dev, in every production and staging environment
- Register your webhook endpoint in Stripe before going live — test mode and live mode have separate endpoints
- Set NEXTAUTH_URL to your production domain exactly as it appears in Stripe's allowed redirects
- Use Stripe CLI in staging to replay events after fixing webhook bugs rather than manually triggering them
FAQ
Can I use these prompts with Claude or ChatGPT?
Yes. They work with any capable model. Claude tends to produce cleaner TypeScript and follows file structure instructions more precisely. Either way, paste in your existing files for best results.
Do these prompts work with Next.js Pages Router?
Most do not without modification. They assume App Router conventions — server components, route handlers in app/api, and layout files. Pages Router uses getServerSideProps and pages/api, which are structured differently.
Which Stripe SDK version do these prompts target?
Stripe Node.js SDK v14 with API version 2023-10-16. If you are on an older SDK, the main difference is the subscription update syntax and some webhook event field names.
Do I need all 50 prompts or can I pick sections?
You can pull any section independently, but run the setup prompts (1–8) first if starting from scratch. Later prompts reference lib/auth.ts, lib/stripe.ts, and the Prisma schema created in those early steps.
What database does this assume?
PostgreSQL via Prisma, which is the most common choice for production SaaS. The Prisma schema and query patterns work identically with MySQL. For MongoDB, the Prisma adapter has minor differences around relation syntax.
Related free tools
If you want to turn this topic into action, use one of ShortIQ's free tools for campaign planning, UTM structure, or QR distribution.
Continue Reading
Explore more guides on link shortener SaaS strategy, Bitly alternatives, and white label link management.
Was this article helpful?
Tell us if this guide solved the problem or what was still missing. We use this to improve the blog and only follow up if you explicitly allow it.