This walks through exactly what [`connect-example`](https://github.com/cosmoops-com/connect-example)
— the reference Next.js integration — actually has in it. Nothing here is
simplified for the guide; if a step needs an argument, so does that app.

## Prerequisites

You'll need an application in Connect with SSO enabled, and its **Connect client** section
open — that's where `CONNECT_API_KEY` and `CONNECT_CLIENT_ID` come from.

<Steps>

<Step>

### Install

```bash
npm install @cosmoops/connect-sdk
```

Or scaffold straight into an existing Next.js App Router project:

```bash
npx @cosmoops/connect-sdk init
```

This writes the bridge route and a `/plans` page for you — steps 2 and 5
below. It won't touch your layout or stylesheet, since those already exist
in any real app.

</Step>

<Step>

### Set your environment variables

```bash title=".env.local"
CONNECT_API_KEY=sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
CONNECT_CLIENT_ID=cid_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

<Callout title="One credential, three jobs">
  `CONNECT_API_KEY` is also your OAuth2 `client_secret` and the key your
  `id_token` is signed with. Revoking it revokes all three at once.
</Callout>

Only these two — there's no third, browser-visible variable to keep in sync.
The default sign-in flow bounces the browser to your own `/signin` route,
which already knows `client_id` server-side; nothing needs it client-side too.

Two optional ones exist — `CONNECT_APP_URL` to talk to a non-production
Connect instance and `CONNECT_SESSION_COOKIE` to rename the session cookie.
Both are covered in [Environments](/docs/environments), along with what a
local setup needs that a deployed one doesn't.

</Step>

<Step>

### Mount the bridge

```ts title="app/api/connect/[...connect]/route.ts"
export { GET, POST } from "@cosmoops/connect-sdk/next";
```

One file, no arguments — credentials come from the environment you just set.
This single route answers `/signin`, `/callback`, and the
`/session` `/entitlements` `/plans` `/addons` `/apps` `/sessions` `/logout`
reads every component below calls. `GET` and `POST` are the same function;
it routes on method and path itself.

</Step>

<Step>

### Wrap your app in the provider

```tsx title="app/layout.tsx"
import { connectProviderProps } from "@cosmoops/connect-sdk/next";
import { ConnectProvider } from "@cosmoops/connect-sdk/client";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <ConnectProvider {...connectProviderProps()}>{children}</ConnectProvider>
      </body>
    </html>
  );
}
```

One session read is shared by every Connect component below it, so a page with
a user menu, an apps grid and a plan table makes one request, not three.

The spread is the only prop you need, and it is worth the second import: a root
layout is a Server Component, so `connectProviderProps()` reads
`CONNECT_APP_URL` there and hands the browser the same Connect origin your
server is using. Leave it off and the two halves of your integration can
address different instances — see
[Environments](/docs/environments#the-browser-half-points-somewhere-too). In
production it resolves to the public instance, so this line is correct
everywhere and you never edit it again.

Everything else is optional: `basePath` if you mounted the bridge somewhere
other than `/api/connect`, and `clientId` + `redirectUri` only if you build the
authorize URL in the browser yourself.

</Step>

<Step>

### Add the theme

```css title="app/globals.css"
@import "tailwindcss";
@import "@heroui/styles";
@import "@cosmoops/connect-sdk/theme.css";
```

Order matters: Tailwind first, HeroUI's token layer next, so the shared
theme's values win. It has to be imported from your *own* CSS file, not
from `node_modules`, or Tailwind roots its content scan inside
`node_modules` and skips your app entirely.

</Step>

<Step>

### Register the redirect URI

Back in the application's Connect client section, add:

```
{your origin}/api/connect/callback
```

This is the one step nothing here can do for you — it has to happen inside
Connect itself. Connect matches a redirect URI exactly, so every origin you
run from needs its own entry — localhost included. See
[Environments](/docs/environments#one-redirect-uri-per-origin).

</Step>

<Step>

### Drop in a pricing page

```tsx title="app/plans/page.tsx"
export { ConnectPlansPage as default } from "@cosmoops/connect-sdk/client";
```

Readiness banner, current subscription, plan grid, and the full
subscribe / upgrade / downgrade / switch-cadence / cancel flow — one line.
Want your own layout instead? `connect-example`'s `/pricing` page composes
the same flow by hand from `groupPlansByTier`, `resolveAction`,
`ConnectSubscribeDialog` and `useConnectCheckout` — read that when this
isn't enough.

</Step>

</Steps>

## That's the whole integration

Run it and you have the full round trip: sign-in, sign-up, session, plans,
subscribe, entitlements, sign-out. Nothing here is `next.config.ts`
boilerplate either — not even `transpilePackages`: `dist/` already ships
ES2022 with `"use client"` hoisted, so Turbopack and webpack both consume it
as-is.

<Callout type="info" title="See it running">
  [`connect-example`](https://github.com/cosmoops-com/connect-example) is
  this exact integration, deployed — session handling, readiness gates, the
  plan grid, every component from `/client` shown live with what it renders
  when it doesn't apply. Clone it or read the source directly; it installs
  `@cosmoops/connect-sdk` from npm like any other consumer, not from this
  monorepo.
</Callout>

## Server-side reads

Inside a Server Component, Route Handler, or Server Action, read the same
session with no argument and no `headers()` dance:

```tsx title="app/dashboard/page.tsx"
import { requireConnectSession } from "@cosmoops/connect-sdk/next";

export default async function DashboardPage() {
  const session = await requireConnectSession(); // UserResponse — redirects if signed out
  return <h1>{session.user.workspaceName}</h1>;
}
```

`requireConnectSession()` is `getConnectSession()` plus the
`if (!session) redirect(...)` you'd otherwise write by hand — reach for the
plain `getConnectSession()` (returns `UserResponse | null`) instead when
"signed out" means something other than "redirect to sign-in." Both are
memoized per request, so calling one from a layout and again from the page
beneath it costs one round trip to Connect, not two — see
[Authentication & authorization](/docs/authentication) for the full shape.

It reads `cookies()` transitively, so under `cacheComponents` this belongs
inside a `<Suspense>` boundary like any other request-scoped read.

## Next steps

- **[Authentication & authorization](/docs/authentication)** — the OAuth2
  round trip this bridge runs for you, `proxy.ts` route protection, and
  role/entitlement-based access control.
- **[Reference](/docs/reference)** — every export across every entry point,
  generated from source.
- **Gate a feature on entitlements** — `connect.entitlements.getReadiness`
  and `blockingBlockers` (isomorphic, safe to import in a Client Component
  too) are covered in the reference under `entitlements`.
- **Not on Next.js?** `@cosmoops/connect-sdk/server`'s `createConnectHandler`
  takes an explicit session store instead of assuming cookies — see its
  reference page for the shape.