Skip to content

🔥 feat(nexts): Introducing bound Optimization components#355

Draft
Charles Hudson (phobetron) wants to merge 1 commit into
mainfrom
NT-3560_nextjs-ssr-hybrid-metaframework-fix
Draft

🔥 feat(nexts): Introducing bound Optimization components#355
Charles Hudson (phobetron) wants to merge 1 commit into
mainfrom
NT-3560_nextjs-ssr-hybrid-metaframework-fix

Conversation

@phobetron

@phobetron Charles Hudson (phobetron) commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

This PR introduces createNextjsOptimizationComponents() as the primary public Next.js SDK integration API. Consumers can now bind Optimization SDK configuration once in an app-local module and use the returned OptimizationRoot, OptimizationProvider, OptimizedEntry, and route trackers across server and client rendering boundaries.

The change also extends the React Web OptimizedEntry render-prop contract with a shared render context that includes getMergeTagValue, updates the Next.js SSR and Hybrid reference implementations to use the new API, and refreshes the docs around consent, locale handling, profile handoff, tracking, and merge tag rendering.

Example usage

// lib/optimization.ts
import { createNextjsOptimizationComponents } from '@contentful/optimization-nextjs'
import { getAppConsent } from './consent'

export const { NextAppAutoPageTracker, OptimizationRoot, OptimizedEntry } =
  createNextjsOptimizationComponents({
    clientId: process.env.NEXT_PUBLIC_CONTENTFUL_OPTIMIZATION_CLIENT_ID ?? '',
    environment: process.env.NEXT_PUBLIC_CONTENTFUL_OPTIMIZATION_ENVIRONMENT ?? 'main',
    locale: 'en-US',
    defaults: { consent: false, persistenceConsent: false },
    server: {
      enabled: true,
      consent: ({ cookies }) =>
        getAppConsent(cookies) ? { events: true, persistence: true } : false,
    },
    trackEntryInteraction: { views: true, clicks: true, hovers: true },
  })
// app/layout.tsx
import { NextAppAutoPageTracker, OptimizationRoot } from '@/lib/optimization'
import { Suspense, type ReactNode } from 'react'

export default async function RootLayout({ children }: { children: ReactNode }) {
  return (
    <html lang="en">
      <body>
        <OptimizationRoot>
          <Suspense>
            <NextAppAutoPageTracker initialPageEvent="skip" />
          </Suspense>
          {children}
        </OptimizationRoot>
      </body>
    </html>
  )
}
// app/page.tsx or a component
import { OptimizedEntry } from '@/lib/optimization'

export function Hero({ entry }) {
  return (
    <OptimizedEntry baselineEntry={entry}>
      {(resolvedEntry, { getMergeTagValue }) => (
        <HeroCard entry={resolvedEntry} getMergeTagValue={getMergeTagValue} />
      )}
    </OptimizedEntry>
  )
}

Architectural and design changes

  • Adds a public root Next.js SDK entrypoint for createNextjsOptimizationComponents().
  • Uses conditional package exports so the same app-local API resolves to the server implementation in server-rendered contexts and the client implementation in browser/client contexts.
  • Moves server request work into the bound server implementation: cookies, headers, request consent, server Optimization data, entry resolution, merge-tag profile reads, and serverOptimizationState handoff.
  • Keeps /server, /client, /esr, /request-handler, /tracking-attributes, and /api-schemas as lower-level escape hatches for custom flows.
  • Removes the previous empty root index.ts entrypoint.
  • Removes NextjsOptimizationState in favor of serverOptimizationState handoff through root/provider components, or automatic handoff through the bound component factory.
  • Extends React Web OptimizedEntry render props from (resolvedEntry) to (resolvedEntry, context), where context.getMergeTagValue is available to rich text renderers.
  • Adds server-component tests for the new automatic path and updates client/React Web tests for the new public contract.

Benefits to consumers

  • Less integration boilerplate: consumers no longer need to manually wire cookies(), headers(), server SDK instances, request helpers, page-level state markers, and matching client provider props for the common path.
  • One app-local import surface works across server and client components.
  • Server-to-browser state handoff is harder to forget or duplicate.
  • Consent and persistence policy live next to SDK configuration instead of being scattered through layouts and pages.
  • Entry rendering, interaction tracking, and merge-tag rendering use the same OptimizedEntry shape across SSR and hybrid patterns.
  • Manual APIs remain available when an app needs direct request SDK control.

Benefits to maintainers

  • The recommended integration path is centralized in one factory instead of being assembled differently in every guide and reference app.
  • Server/client boundary behavior is easier to test because the bound factory owns the contract.
  • Docs can explain one primary path, with lower-level APIs documented as escape hatches.
  • Reference implementations now exercise the preferred public API directly.
  • Merge-tag rendering has a clearer framework-level contract instead of relying on separate hook usage inside nested renderers.

Reference implementation rendering improvements

Beyond adopting bound optimization components, the Next.js SSR and Hybrid examples also improve rendering structure:

  • Extract shared EntryCardContent rendering so server and client entry cards use the same rich text, nested-entry, click-target, and test-attribute behavior.
  • Pass getMergeTagValue from OptimizedEntry render props into rich text render options, aligning merge-tag rendering with the resolved entry/profile context.
  • Simplify page components by removing local optimization-data plumbing and manual resolver maps.
  • Update SSR entry rendering to use the same entry-card content path for normal entries, nested entries, and live-update islands.
  • Re-enable the SSR JavaScript-disabled E2E path by removing SKIP_NO_JS from the SSR example env flags.

[NT-3560]

@phobetron Charles Hudson (phobetron) marked this pull request as draft July 3, 2026 13:13
@phobetron Charles Hudson (phobetron) force-pushed the NT-3560_nextjs-ssr-hybrid-metaframework-fix branch 2 times, most recently from 724deea to 9508d2d Compare July 3, 2026 16:47
Introduce createNextjsOptimizationComponents() as the primary Next.js SDK integration surface. The factory binds application config once and returns app-local OptimizationRoot, OptimizationProvider, OptimizedEntry, and route tracker components that resolve to the correct server or client runtime implementation.

- Add the root @contentful/optimization-nextjs entrypoint for bound component creation
- Add automatic server components that resolve request consent, cookies, headers, server optimization data, entry variants, merge tags, and server-to-browser state handoff
- Add the matching client component factory that reuses the same app-local API while removing server-only config
- Remove the empty root entrypoint and make the package default runtime point at the client build with a react-server conditional export
- Replace page-level NextjsOptimizationState usage with bound root/provider serverOptimizationState handoff
- Extend React Web OptimizedEntry render props with getMergeTagValue and export OptimizedEntryRenderContext
- Update SSR and hybrid Next.js reference implementations to use app-local bound components for server first paint, browser takeover, tracking, and merge tag rendering
- Share entry-card rich text rendering helpers across server and client examples
- Update Next.js, React Web, and conceptual docs for the new bound component model, manual escape hatches, consent, locale, profile synchronization, tracking, and merge tag guidance
- Add tests for automatic server components and update client and React Web tests for the new render context

BREAKING CHANGE: NextjsOptimizationState is removed from the client entrypoint. Pass server optimization data through OptimizationRoot or OptimizationProvider via serverOptimizationState, or use createNextjsOptimizationComponents() to let the bound root/provider handle server-to-browser state handoff.

[[NT-3560](https://contentful.atlassian.net/browse/NT-3560)]
@phobetron Charles Hudson (phobetron) force-pushed the NT-3560_nextjs-ssr-hybrid-metaframework-fix branch from 9508d2d to 75f0298 Compare July 3, 2026 17:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant