On this page
  1. 1. Install one fixed package version
  2. 2. Build the server service first
  3. 3. Hand only the safe session to React
  4. 4. Use the router-specific adapter where it fits
  5. 5. Add route capabilities independently
  6. Verify the boundary
  7. Optional application UI

Getting started with Next.js

Add Playstack authentication and client session state to a Next.js application, then extend it with route-level capabilities.

This path composes Playstack authentication around an existing Next.js application. Auth.js can continue to own provider handshakes and cookies while Playstack owns normalized identities, account linking policy, opaque sessions, and the safe session projection exposed to application code.

1. Install one fixed package version

Before you start: this is an integration guide for an existing Next.js app, not a complete sign-up server. For a runnable first program, try the storage quickstart. Confirm package access before installing.

The composition examples below require these application-owned pieces:

  • auth, including the example's authenticateProvider wrapper: your configured identity service and provider-login policy. Start with Auth and the Auth.js bridge.
  • activeAccountFor and accounts.ensurePersonalAccount: your account-selection and onboarding functions, not exports supplied by the bridge. See Accounts.
  • readCsrfCookie, session resolvers and HTTP routes: your cookie/session transport. See Auth React and Auth Next.
  • Dashboard, feedDefinition and streamPublishedPosts: your UI and product content, needed only for the examples that use them.

Keep server services out of browser bundles. Auth.js is one optional composition, not a prerequisite for using Playstack in Next.js.

Install the portable capability and only the edge bindings you need:

sh
npm install @playstack/auth @playstack/auth-authjs @playstack/auth-react next-auth

Add @playstack/auth-next when either router needs server-side session handoff or protection. Add @playstack/feeds and @playstack/feeds-next only when the application publishes feeds or sitemaps.

2. Build the server service first

Create the @playstack/auth service in a server-only composition module. The application supplies persistence, time, identifiers, secrets, and event delivery. Export the configured service rather than reconstructing it in individual route files.

Provider profile normalization is application policy. Connect Auth.js only after that policy is explicit:

ts
import { createAuthJsBridge } from '@playstack/auth-authjs'

export const authJsBridge = createAuthJsBridge({
  normalizeProfile: ({ provider, providerAccountId, profile }) => ({
    providerId: provider,
    providerUserId: providerAccountId,
    email: profile.email,
    emailVerified: profile.email_verified === true,
    name: profile.name,
    avatarUrl: profile.avatar_url,
    raw: profile,
  }),
  authenticate: (profile) => auth.authenticateProvider(profile),
  resolveAccount: (userId) => activeAccountFor(userId),
  onAuthenticated: (user) => accounts.ensurePersonalAccount(user.id),
})

Attach authJsBridge.callbacks to the application's Auth.js configuration. Do not enable dangerous email account linking: the portable auth service owns linking decisions, and the application must state whether a provider email is verified.

3. Hand only the safe session to React

Connect the application routes expected by the React transport, then place one provider near the application root:

tsx
import { PlaystackAuthProvider, createFetchAuthTransport } from '@playstack/auth-react'

const transport = createFetchAuthTransport({
  headers: () => ({ 'x-csrf-token': readCsrfCookie() }),
})

export function ApplicationProviders({ children, session }) {
  return (
    <PlaystackAuthProvider transport={transport} initialSession={session}>
      {children}
    </PlaystackAuthProvider>
  )
}

This is the same-origin cookie transport: the Next routes and the page share an origin, so the CSRF cookie is readable and the session cookie is ambient. A Next app talking to an API on another origin uses createBearerAuthTransport instead, without a CSRF header (see the auth-react package page). The initial value is the narrow Playstack session view, never the Auth.js provider token or raw profile. Pass null when the server has established that the request is unauthenticated; omit the property only when the client should load the session itself.

Client guards improve navigation but do not protect data. Route handlers, server actions, API operations, and server-rendered pages must validate the session and account scope again at their trusted boundary.

4. Use the router-specific adapter where it fits

Pages Router applications can protect SSR handlers with @playstack/auth-next/pages:

ts
import { createAuthNextHelpers } from '@playstack/auth-next/pages'

const { requireSession } = createAuthNextHelpers({
  resolveSession: ({ req }) => resolveSafeSessionView(req),
})

export const getServerSideProps = requireSession(
  { redirectTo: '/login' },
  async ({ session }) => ({ props: { userId: session.user.id } }),
)

App Router applications use the dedicated structural helper while retaining Next's redirect and request behavior at the application edge:

tsx
import { redirect } from 'next/navigation'

import { createAppRouterAuthHelpers } from '@playstack/auth-next/app'

const auth = createAppRouterAuthHelpers({
  resolveSession: () => resolveSessionViewFromCookies(),
})

export default async function DashboardPage() {
  const session = await auth.requireSession({
    redirectTo: '/login',
    onUnauthenticated: (destination) => redirect(destination ?? '/login'),
  })

  return <Dashboard userId={session.user.id} />
}

The same helper wraps route handlers and passes the original request and route context through unchanged. Middleware, React Server Component cache policy, and server-action authorization remain explicit application decisions.

5. Add route capabilities independently

For example, a small feed can be exposed from an App Router route without introducing a Next.js runtime dependency into the feed domain:

ts
import { createInlineFeedRoute } from '@playstack/feeds-next'

export const GET = createInlineFeedRoute({
  format: 'rss',
  definition: feedDefinition,
  entries: () => streamPublishedPosts(),
  content: 'full',
  cacheControl: 'public, max-age=60',
})

See @playstack/feeds-next for generated-document storage, dynamic paths, and failure behavior.

Verify the boundary

Before adding another package, confirm that:

  • provider tokens and raw profiles remain server-only;
  • all Playstack packages use one version;
  • server operations validate session and account scope;
  • client providers receive only safe serializable projections;
  • framework, provider, and database clients are constructed by the application;
  • each additional adapter corresponds to a lifecycle boundary the application actually uses.

Optional application UI

Chakra, App UI and Admin UI add themes, controlled application compositions and the initial scoped members section. These are independent of auth/backend adoption. The host owns routes, data, mutations and authorization; Next Client Components hold callbacks and the Chakra system. See Application interfaces.

Go

Playstack Pro tag
OriginsPricingBlogNewsletterChangelogStatusRoadmap
ContributorsCommunityIn Use ShowcaseCase StudiesPartnersSponsors
FAQsSupportContact

© 2026 Playstack. All rights reserved.

With OSS
Terms of ServicePrivacy PolicyCookie PolicyImprint

By

Commune Software