Skip to content

Next.js

Next.js apps usually use Guapocado in two places:

  • Server code with @guapocado/sdk.
  • Client UI with @guapocado/react and a client key.

Keep server keys on the server. Anything prefixed with NEXT_PUBLIC_ is exposed to the browser.

Terminal window
GUAPOCADO_API_KEY=sk_guap_test_...
NEXT_PUBLIC_GUAPOCADO_CLIENT_KEY=ck_guap_test_...

Create a small helper for request-scoped billing:

src/lib/billing.ts
import { createGuapocadoClient } from "@guapocado/sdk";
export function createBilling(customerId: string) {
return createGuapocadoClient({
apiKey: process.env.GUAPOCADO_API_KEY!,
customerId,
});
}

In a real app, customerId should come from your auth/session layer.

src/app/api/features/[feature]/route.ts
import { createBilling } from "@/lib/billing";
import { NextResponse } from "next/server";
export async function GET(
request: Request,
{ params }: { params: Promise<{ feature: string }> },
) {
const { feature } = await params;
const { searchParams } = new URL(request.url);
const customerId = searchParams.get("customerId");
if (!customerId) {
return NextResponse.json({ error: "customerId required" }, { status: 400 });
}
const guap = createBilling(customerId);
const hasAccess = await guap.has(feature);
return NextResponse.json({ feature, hasAccess });
}
src/app/api/billing/checkout/[productKey]/route.ts
import { createBilling } from "@/lib/billing";
import { redirect } from "next/navigation";
export async function POST(
request: Request,
{ params }: { params: Promise<{ productKey: string }> },
) {
const { productKey } = await params;
const form = await request.formData();
const customerId = String(form.get("customerId") ?? "");
if (!customerId) {
return Response.json({ error: "customerId required" }, { status: 400 });
}
const origin = new URL(request.url).origin;
const guap = createBilling(customerId);
await guap.customers.create({ id: customerId });
const checkout = await guap.checkout.create({
productKey,
successUrl: `${origin}/billing/success`,
cancelUrl: `${origin}/billing`,
});
redirect(checkout.url);
}
"use server";
import { createBilling } from "@/lib/billing";
export async function summarizeText(formData: FormData) {
const customerId = String(formData.get("customerId") ?? "");
const text = String(formData.get("text") ?? "");
const credits = Math.ceil(text.length / 4);
const guap = createBilling(customerId);
await guap.usage.consume("ai-credits", credits);
try {
return await summarize(text);
} catch (error) {
await guap.usage.refund("ai-credits", credits);
throw error;
}
}

Use @guapocado/react for browser-safe reads.

"use client";
import { GuapocadoProvider } from "@guapocado/react";
import type { ReactNode } from "react";
export function BillingProvider({
children,
customerId,
}: {
children: ReactNode;
customerId: string;
}) {
return (
<GuapocadoProvider
apiKey={process.env.NEXT_PUBLIC_GUAPOCADO_CLIENT_KEY!}
customerId={customerId}
>
{children}
</GuapocadoProvider>
);
}

Then read feature state in client components:

"use client";
import { useEntitlement } from "@guapocado/react";
export function AnalyticsLink() {
const analytics = useEntitlement("advanced-analytics");
if (analytics.loading) return <span>Checking...</span>;
if (!analytics.has) return <a href="/billing">Upgrade</a>;
return <a href="/analytics">Open analytics</a>;
}

For newer teams, keep all mutations server-side:

  • checkout
  • usage consume
  • usage refund
  • subscription change
  • customer create
  • limit settings

Use client components only for browser-safe reads and display.