---
title: Getting started with Next.js, TypeScript, and Stripe Checkout
description: Add payments functionality to your Next.js applications with Stripe and deploy to Vercel.
url: /kb/guide/getting-started-with-nextjs-typescript-stripe
canonical_url: "https://vercel.com/kb/guide/getting-started-with-nextjs-typescript-stripe"
last_updated: 2026-06-15
authors: Lee Robinson
related:
  - /docs/concepts/deployments/git
  - /docs/concepts/deployments/preview-deployments
  - /docs/concepts/deployments/environments
install_vercel_plugin: npx plugins add vercel/vercel-plugin
---

This guide walks you through setting up a Next.js project with TypeScript and adding payments functionality with Stripe Checkout.

## Step 1: Setting Up a TypeScript Project with Next.js

Setting up a TypeScript project with Next.js is very convenient, as it automatically generates the [tsconfig.json](https://github.com/vercel/next.js/tree/canary/examples/with-stripe-typescript/tsconfig.json) configuration file for you. You can follow the setup steps in the [docs](https://nextjs.org/docs/app/building-your-application/configuring/typescript). You can also find the full example that we're looking at in detail below, on [GitHub](https://github.com/vercel/next.js/tree/canary/examples/with-stripe-typescript).

To create a pre-configured Next.js TypeScript project locally, execute [create-next-app](https://nextjs.org/docs/app/api-reference/cli/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/):

`npx create-next-app --example with-stripe-typescript my-stripe-project && cd my-stripe-project # or yarn create next-app --example with-stripe-typescript my-stripe-project && cd my-stripe-project`

### Managing API Keys with Next.js & Vercel

When working with API keys and secrets, you need to make sure to keep them out of version control. That's why you should set these as environment variables. Find more details on how to organise your `.env` files in the [Next.js docs](https://nextjs.org/docs/basic-features/environment-variables).

At the root of your project add a `.env.local` file and provide the Stripe API keys from your [Stripe Dashboard](https://stripe.com/docs/development#api-keys).

`# Stripe keys NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_12345 STRIPE_SECRET_KEY=sk_12345`

The `NEXT_PUBLIC_` prefix automatically exposes this variable to the browser. Next.js will insert the value for these into the publicly viewable source code at build/render time. Therefore make sure to not use this prefix for secret values!

Make sure to add `.env*.local` to your [.gitignore file](https://github.com/vercel/next.js/blob/canary/examples/with-stripe-typescript/.gitignore) to tell git to not track your secrets. If you created the project with `create-next-app`, the `.gitignore` file is already set up for you.

### Loading Stripe.js

Due to [PCI compliance requirements](https://stripe.com/docs/security), the Stripe.js library has to be loaded from Stripe's servers. This creates a challenge when working with server-side rendered apps, as the window object is not available on the server. To help you manage this, Stripe provides a [loading wrapper](https://github.com/stripe/stripe-js) that allows you to import Stripe.js as an ES module:

`import { loadStripe } from '@stripe/stripe-js'; const stripe = await loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);`

Stripe.js is loaded as a side effect of the `import '@stripe/stripe-js';` statement. If you prefer to delay loading of Stripe.js until Checkout, you can `import {loadStripe} from '@stripe/stripe-js/pure';`. Find more details on the various options in the [Stripe docs](https://stripe.com/docs/disputes/prevention/advanced-fraud-detection#disabling-advanced-fraud-detection).

To optimize your site's performance you can hold off instantiating Stripe until the first render of your checkout page. To make sure that you don't reinstate Stripe on every render, we recommend that you use the singleton pattern to create/retrieve the Stripe instance:

`import { Stripe, loadStripe } from '@stripe/stripe-js'; let stripePromise: Promise<Stripe | null>; const getStripe = () => { if (!stripePromise) { stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!); } return stripePromise; }; export default getStripe;`

## Step 2: Creating a CheckoutSession and Redirecting to Stripe Checkout

[Stripe Checkout](https://stripe.com/checkout) is the fastest way to get started with Stripe and provides a stripe-hosted checkout page that comes with various payment methods and support for Apple Pay and Google Pay out of the box.

In your `app/actions/` directory, create a [Server Action](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations) called `stripe.ts`. In this function, create a new [CheckoutSession](https://stripe.com/docs/api/checkout/sessions/create) and return the session URL to redirect the user to Stripe.

``// Partial of app/actions/stripe.ts "use server"; import type { Stripe } from "stripe"; import { stripe } from "@/lib/stripe"; import { formatAmountForStripe } from "@/utils/stripe-helpers"; import { CURRENCY } from "@/config"; export async function createCheckoutSession( data: FormData, ): Promise<{ client_secret: string | null; url: string | null }> { const checkoutSession: Stripe.Checkout.Session = await stripe.checkout.sessions.create({ mode: "payment", submit_type: "donate", line_items: [{ quantity: 1, price_data: { currency: CURRENCY, product_data: { name: "Custom amount donation" }, unit_amount: formatAmountForStripe( Number(data.get("customDonation") as string), CURRENCY, ), }, }, ], success_url: `${process.env.NEXT_PUBLIC_BASE_URL}/donate-with-checkout/result?session_id={CHECKOUT_SESSION_ID}`, cancel_url: `${process.env.NEXT_PUBLIC_BASE_URL}/donate-with-checkout`, }); return { client_secret: checkoutSession.client_secret, url: checkoutSession.url, }; }`` Next, create a `CheckoutForm` component in `app/components/CheckoutForm.tsx` that calls the Server Action above and redirects the user to Stripe. `// Partial of app/components/CheckoutForm.tsx "use client"; import { createCheckoutSession } from "@/actions/stripe"; const formAction = async (data: FormData): Promise<void> => { const { url } = await createCheckoutSession(data); window.location.assign(url as string); }; return ( <form action={formAction}> <input type="hidden" name="uiMode" value="hosted" /> {/* donation input */} <button type="submit">Donate</button> </form> );` Use this component in your checkout page at `app/donate-with-checkout/page.tsx`. `import type { Metadata } from "next"; import CheckoutForm from "@/components/CheckoutForm"; export const metadata: Metadata = { title: "Donate with hosted Checkout | Next.js + TypeScript Example", }; export default function DonatePage(): JSX.Element { return ( <div className="page-container"> <h1>Donate with hosted Checkout</h1> <p>Donate to our project 💖</p> <CheckoutForm uiMode="hosted" /> </div> ); }` ## Step 3: Handling Webhooks & Checking Their Signatures Webhook events allow you to get notified about events that happen on your Stripe account. This is especially useful for [asynchronous payments](https://stripe.com/docs/payments/payment-intents/verifying-status#webhooks), subscriptions with [Stripe Billing](https://stripe.com/docs/billing/webhooks), or building a marketplace with [Stripe Connect](https://stripe.com/docs/connect/webhooks).

Create a Route Handler at `app/api/webhooks/route.ts` to receive Stripe webhook events. To make sure that a webhook event was sent by Stripe, not by a malicious third party, you need to [verify the webhook event signature](https://stripe.com/docs/webhooks/signatures#verify-official-libraries):

``// Partial of app/api/webhooks/route.ts import type { Stripe } from "stripe"; import { NextResponse } from "next/server"; import { stripe } from "@/lib/stripe"; export async function POST(req: Request) { let event: Stripe.Event; try { event = stripe.webhooks.constructEvent( await (await req.blob()).text(), req.headers.get("stripe-signature") as string, process.env.STRIPE_WEBHOOK_SECRET as string, ); } catch (err) { const errorMessage = err instanceof Error ? err.message : "Unknown error"; console.log(`❌ Error message: ${errorMessage}`); return NextResponse.json( { message: `Webhook Error: ${errorMessage}` }, { status: 400 }, ); } console.log("✅ Success:", event.id); // ...``

This way your Route Handler only processes requests whose signatures were signed by Stripe.

## Step 4: Deploy with Vercel

To deploy your Next.js + Stripe Checkout site with [Vercel for Git](https://vercel.com/docs/concepts/deployments/git), make sure it has been pushed to a Git repository.

Import the project into Vercel using your [Git provider](https://vercel.com/import/git) of choice.

After your project has been imported, all subsequent pushes to branches will generate [Preview Deployments](https://vercel.com/docs/concepts/deployments/preview-deployments#), and all changes made to the [Production Branch](https://vercel.com/docs/concepts/deployments/git#production-branch) (commonly "main") will result in a [Production Deployment](https://vercel.com/docs/concepts/deployments/environments#production).

Once deployed, you will get a URL to see your site live, such as the following: [https://nextjs-typescript-react-stripe-js.vercel.app/](https://nextjs-typescript-react-stripe-js.vercel.app/)

Set up a Next.js + Stripe Checkout site with a few clicks using the Deploy button, and create a Git repository for it in the process for automatic deployments for your updates.