Stripe Connect in Next.js

With server actions
Aug 28th 2025

This is what you need to do:

src/components/stripe/provider.tsx
"use client";

import { useCallback, useEffect, useState } from "react";
import { createAccountSessionAction } from "@/actions";

import { ConnectComponentsProvider } from "@stripe/react-connect-js";
import {
  type AppearanceOptions,
  type StripeConnectInstance,
  loadConnectAndInitialize,
} from "@stripe/connect-js";

export default function StripeConnectProvider({
  userName,
  children,
}: {
  userName: string;
  children: React.ReactNode;
}) {
  const [stripeConnectInstance, setStripeConnectInstance] =
    useState<StripeConnectInstance | null>(null);

  async function fetchClientSecret() {
   return await createAccountSessionAction({ userName }),
  }

  useEffect(() => {
    if (!userName || stripeConnectInstance) return;
    const publishableKey = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY;
    if (!publishableKey) {
      throw new Error("stripe publishable key is not set");
    }

    setStripeConnectInstance(
      loadConnectAndInitialize({
        fetchClientSecret,
        fonts,
        publishableKey,
        appearance,
      })
    );
  }, [userName, fetchClientSecret, stripeConnectInstance]);

  return (
    stripeConnectInstance && (
      <ConnectComponentsProvider connectInstance={stripeConnectInstance}>
        {children}
      </ConnectComponentsProvider>
    )
  );
}

Notice how I'm importing createAccountSessionAction from "@/actions".

For styling:

src/components/stripe/provider.tsx
import colors from "tailwindcss/colors";

const INTER_URL =
  "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap";

const fonts = [
  {
    cssSrc: INTER_URL,
  },
];

const appearance: AppearanceOptions = {
  variables: {
    colorBackground: colors.neutral[950],
    colorPrimary: colors.neutral[100],
    colorText: colors.neutral[100],
    colorSecondaryText: colors.neutral[500],
    formBackgroundColor: colors.neutral[900],
    formAccentColor: colors.neutral[300],
    offsetBackgroundColor: colors.neutral[900],
    overlayBackdropColor: "rgba(10, 10, 10, 0.8)",
    actionSecondaryColorText: colors.neutral[700],
    buttonSecondaryColorBackground: colors.neutral[900],
    buttonSecondaryColorBorder: colors.neutral[700],
    buttonSecondaryColorText: colors.neutral[50],
    colorBorder: colors.neutral[700],
    colorDanger: colors.red[600],
    spacingUnit: "10px",
    actionPrimaryTextDecorationLine: "underline",
    headingXlFontWeight: "400",
    labelMdFontWeight: "400",
    bodyMdFontWeight: "400",
    labelSmFontWeight: "200",
    bodySmFontWeight: "400",
    borderRadius: "24px",
    buttonBorderRadius: "24px",
    formBorderRadius: "8px",
    badgeBorderRadius: "24px",
    overlayBorderRadius: "24px",
    badgeSuccessColorBackground: colors.emerald[950],
    badgeSuccessColorText: colors.emerald[300],
    badgeSuccessColorBorder: colors.emerald[950],
    badgeDangerColorBackground: colors.red[950],
    badgeDangerColorText: colors.red[300],
    badgeWarningColorBackground: colors.amber[950],
    badgeWarningColorText: colors.amber[300],
    badgeWarningColorBorder: colors.amber[950],
    badgeNeutralColorBackground: colors.neutral[800],
    badgeNeutralColorText: colors.neutral[50],
    badgeNeutralColorBorder: colors.neutral[800],
  },
};

I use the Tailwind colors directly. And to change the font I get the url from google fonts. That's the only way I found that works.

The createAccountSessionAction looks like this:

src/actions.ts
const createAccountSessionActionSchema = z.object({
  userName: z.string(),
});

type CreateAccountSessionActionSchema = z.infer<
  typeof createAccountSessionActionSchema
>;

export async function createAccountSessionAction(
  createData: CreateAccountSessionActionSchema
) {
  try {
    const { userId } = await auth();
    if (!userId) throw new Error("User not signed in");
    createAccountSessionActionSchema.parse(createData);
    const user = await readUser(userId);
    if (!user) throw new Error("User not found");
    if (user.userName !== createData.userName) {
      throw new Error("User not authorized");
    }

    if (user.stripeId === null) {
      throw new Error("No stripe account linked");
    }

    const clientSecret = await createAccountSession(user.stripeId);
    if (!clientSecret) throw new Error("Error creating account session");

    return clientSecret;
  } catch (error) {
    await log.error("Error creating account session", {
      error,
      createData,
    });
    throw new Error();
  }

I do some validation and create the session.

This is the createAccountSession function:

src/stripe.ts
export const stripe = new Stripe(STRIPE_SECRET_KEY);

export async function createAccountSession(account: string) {
  try {
    const session = await stripe.accountSessions.create({
      account,
      components: {
        account_onboarding: {
          enabled: true,
          features: {
            external_account_collection: true,
          },
        },
        payments: {
          enabled: true,
          features: {
            capture_payments: true,
            refund_management: true,
            dispute_management: true,
            destination_on_behalf_of_charge_management: true,
          },
        },
        account_management: { enabled: true },
        notification_banner: { enabled: true },
        balances: { enabled: true },
        documents: { enabled: true },
        tax_settings: { enabled: true },
        tax_registrations: { enabled: true },
        payouts: { enabled: true },
        payouts_list: { enabled: true },
        payment_details: {
          enabled: true,
          features: {
            capture_payments: true,
            refund_management: true,
            dispute_management: true,
            destination_on_behalf_of_charge_management: true,
          },
        },
      },
    });

    return session.client_secret;
  } catch (error) {
    await log.error("Error creating account session", error);
    return null;
  }
}

To get the full code check my noos repo.