Skip to content

fix next/pages server-only components from being imported in the client#3533

Open
brandonhines-mialabs wants to merge 2 commits intoPostHog:mainfrom
brandonhines-mialabs:bug/fix-next-pages-import
Open

fix next/pages server-only components from being imported in the client#3533
brandonhines-mialabs wants to merge 2 commits intoPostHog:mainfrom
brandonhines-mialabs:bug/fix-next-pages-import

Conversation

@brandonhines-mialabs
Copy link
Copy Markdown

Problem

@posthog/next/pages doesn't build in a Next.js Pages Router app following the documented setup. The ./pages subpath in package.json#exports had no per-runtime conditions, so an import from pages/_app.tsx transitively pulled in posthog-node and 'server-only'. Next.js rejects 'server-only' in client modules before tree-shaking can drop the unused re-exports, so next build fails with:

'server-only' cannot be imported from a Client Component module.

Pages Router users hit this on every Next version that ships the server-only enforcement (Next 13+).

Changes

Split the ./pages barrel per runtime, mirroring the pattern already used for the . entry:

  • src/pages.client.tsbrowser condition. Re-exports PostHogProvider and PostHogPageView only.
  • src/pages.edge.tsedge-light / edge / worker conditions. Re-exports postHogMiddleware, PostHogPageView, DEFAULT_INGEST_PATH.
  • src/pages.tsreact-server / default conditions. Unchanged; keeps the full Pages Router surface (getServerSidePostHog, getPostHog, etc.).

Wired into package.json#exports:

"./pages": {
    "types": "./dist/pages.d.ts",
    "edge-light": "./dist/pages.edge.js",
    "edge": "./dist/pages.edge.js",
    "worker": "./dist/pages.edge.js",
    "browser": "./dist/pages.client.js",
    "react-server": "./dist/pages.js",
    "default": "./dist/pages.js"
}

Existing consumer imports (from '@posthog/next/pages') keep working unchanged; the resolver picks the right file per runtime.

Libraries affected

  • All of them
  • posthog-js (web)
  • posthog-js-lite (web lite)
  • posthog-node
  • posthog-react-native
  • @posthog/react
  • @posthog/ai
  • @posthog/convex
  • @posthog/next
  • @posthog/nextjs-config
  • @posthog/nuxt
  • @posthog/rollup-plugin
  • @posthog/webpack-plugin
  • @posthog/types

Checklist

  • Tests for new code
  • Accounted for the impact of any changes across different platforms
  • Accounted for backwards compatibility of any changes (no breaking changes!)
  • Took care not to unnecessarily increase the bundle size

If releasing new changes

  • Ran pnpm changeset to generate a changeset file

@vercel
Copy link
Copy Markdown

vercel Bot commented May 5, 2026

@brandonhines-mialabs is attempting to deploy a commit to the PostHog Team on Vercel.

A member of the Team first needs to authorize it.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 5, 2026

Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
packages/next/src/pages.client.ts:1-7
`DEFAULT_INGEST_PATH` is a plain string constant (`'/ingest'`) that carries no `server-only` or `posthog-node` dependency, but it is omitted from this client barrel while still being declared in the shared `pages.d.ts` (generated from `pages.ts`). Because `types` is not conditioned per-runtime, TypeScript always resolves against the full type surface — so `import { DEFAULT_INGEST_PATH } from '@posthog/next/pages'` type-checks cleanly but evaluates to `undefined` at runtime in any browser bundle. A `pages/_app.tsx` that passes `DEFAULT_INGEST_PATH` as `api_host` would silently break PostHog initialisation on the client.

```suggestion
// Client-runtime barrel for the `./pages` subpath. Resolved by Next.js's
// `browser` exports condition. Excludes `getPostHog`, `getServerSidePostHog`,
// and `postHogMiddleware`, which import `server-only` or `posthog-node` and
// must not be reachable from a client bundle.
export { PostHogProvider } from './pages/PostHogProvider.js'
export { PostHogPageView } from './pages/PostHogPageView.js'
export { DEFAULT_INGEST_PATH } from './shared/constants.js'
export type { PagesPostHogProviderProps } from './pages/PostHogProvider.js'
```

### Issue 2 of 2
packages/next/tests/barrelExports.client.test.ts:41-46
**Prefer parameterised tests**

The "omits" and "exposes" checks across both `barrelExports.*.test.ts` files repeat the same `expect(typeof m.X).toBe(...)` / `expect(m.X).toBeUndefined()` pattern once per symbol. Per the project's preference for parameterised tests, these could use `it.each` with `[symbolName, expectedType]` rows — both for conciseness and so that a future export addition shows up as a new row rather than a new `expect` buried in a block. The same pattern applies to the equivalent blocks in `barrelExports.server.test.ts`.

Reviews (1): Last reviewed commit: "fix next/pages server-only components fr..." | Re-trigger Greptile

Comment thread packages/next/src/pages.client.ts
Comment thread packages/next/tests/barrelExports.client.test.ts Outdated
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