[object Object]

Why Go [Headless

Experience Cloud](/articles/general/headless-experience-cloud-patterns/)‘s native Lightning UIs have constraints — theming depth, performance, modern framework integration. Using it headlessly pairs Salesforce identity and data with React/Next.js frontend agility.

Native Aura/LWR Experience Cloud sites struggle below 50 on Lighthouse Performance even with aggressive theming. Bundle sizes commonly exceed 500KB. SEO is workable but constrained. Going headless lets you ship sub-second TTFB on Vercel or Cloudflare Pages while retaining Salesforce as the system of record for identity, accounts, cases, and orders. The trade is operational complexity: two deployment surfaces, two security perimeters, two performance budgets.

Architecture

Next.js on Vercel or similar. Salesforce SCIM/OAuth for auth. Salesforce APIs (REST, GraphQL, Pub/Sub) for data. Data Cloud for unified profile. Frontend builds against a well-defined API contract.

Reference stack. Next.js 15 App Router with React Server Components for SSR. NextAuth.js with the Salesforce provider for OAuth 2.0 Web Server Flow, refresh-token rotation, and PKCE. UI API GraphQL endpoint (/services/data/v62.0/graphql) for record reads with field-level security enforced. Pub/Sub API via gRPC for real-time updates. Data Cloud Connect API for unified-profile lookups. A BFF layer (Next.js route handlers or a Cloudflare Worker) brokers calls so secrets never leave the server.

// app/api/account/route.ts
export async function GET(req: NextRequest) {
  const session = await getServerSession(authOptions);
  const res = await fetch(`${process.env.SF_INSTANCE}/services/data/v62.0/graphql`, {
    method: 'POST',
    headers: { Authorization: `Bearer ${session.accessToken}` },
    body: JSON.stringify({ query: ACCOUNT_QUERY }),
  });
  return Response.json(await res.json());
}

Challenges

Authentication flows need careful handling — session, refresh, logout. CORS configuration. Rate limits on APIs under heavy frontend use. Salesforce’s caching vs. your frontend’s cache strategy.

Rate limits bite first. Experience Cloud login flows count against the org’s daily API allocation; high-traffic sites need API capacity SKUs. Use Salesforce Connected App named credentials with per-user OAuth, not a service account, to preserve field-level security. Cache UI API responses at the BFF with Redis or Cloudflare KV using Salesforce’s If-Modified-Since headers. Logout must revoke the refresh token via the OAuth revoke endpoint, not just clear the client cookie.

When It Fits

Customer portals with specific UX requirements. Mobile-first experiences. Marketing sites that need CRM data. Teams with deep React skills and CRM data dependencies.

Strong fit signals: design system shared across web and mobile, sub-second performance budget, content marketing requiring SEO that LWR can’t deliver, A/B testing or personalization needing edge compute, multi-brand portals with distinct UX. Weak fit: small admin portals, internal apps, low-traffic communities. The operational overhead doesn’t pencil out below roughly 10K monthly active users.

Common Failure Modes

Three patterns. Bypassing field-level security by using a service account, exposing data the user shouldn’t see. Cache invalidation drift between Salesforce and the BFF, showing stale prices or inventory. Refresh-token mismanagement causing silent session loss after 90 minutes — the default Salesforce access-token lifetime.

What to Do This Week

Audit your headless Experience Cloud auth flow to confirm per-user OAuth with refresh-token rotation, not a shared service account.

[object Object]
Share