Skip to content

[Fix] Infinite Loop on handler/sign-in due to useStackApp not being able to find the StackProvider given context#1248

Merged
N2D4 merged 1 commit intodevfrom
fix-useStackApp-bug
Mar 13, 2026
Merged

[Fix] Infinite Loop on handler/sign-in due to useStackApp not being able to find the StackProvider given context#1248
N2D4 merged 1 commit intodevfrom
fix-useStackApp-bug

Conversation

@nams1570
Copy link
Collaborator

@nams1570 nams1570 commented Mar 13, 2026

Context

On preview branches, we were running into an issue where once you hit the handler/sign-in it kept refreshing and looping if you weren't signed in already. This occurred because there were two versions of stack-provider-client that were being generated and the StackProvider was providing context for one version, and the useStackApp was trying to consume context from the other version. This was caused by this PR, due to the changes to js-library.ts (adding the new plugin).

Copilot AI review requested due to automatic review settings March 13, 2026 05:28
@vercel
Copy link

vercel bot commented Mar 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
stack-auth-hosted-components Ready Ready Preview, Comment Mar 13, 2026 5:28am
stack-backend Building Building Preview, Comment Mar 13, 2026 5:28am
stack-dashboard Building Building Preview, Comment Mar 13, 2026 5:28am
stack-demo Building Building Preview, Comment Mar 13, 2026 5:28am
stack-docs Building Building Preview, Comment Mar 13, 2026 5:28am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 13, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 59e60328-c600-4d57-98b6-d15ccff5ca17

📥 Commits

Reviewing files that changed from the base of the PR and between 612cb71 and 96dfbab.

📒 Files selected for processing (1)
  • configs/tsdown/js-library.ts

📝 Walkthrough

Walkthrough

A plugin block responsible for automatically generating an ESM-specific package.json file during the build bundling process has been removed from the TypeScript build configuration. The ESM externalization behavior remains unchanged.

Changes

Cohort / File(s) Summary
Build Configuration
configs/tsdown/js-library.ts
Removed plugin block that emitted esm-specific package.json asset during generateBundle when output directory matched /esm or dist/esm patterns.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

A plugin once wrote files with care,
ESM packages here and there,
But now it's gone—so lean, so clean,
Configuration keeps what it means! 🐰✨

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-useStackApp-bug
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@N2D4 N2D4 merged commit 3e64261 into dev Mar 13, 2026
32 of 37 checks passed
@N2D4 N2D4 deleted the fix-useStackApp-bug branch March 13, 2026 05:28
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the shared tsdown JS library build config by removing the plugin that emits a package.json into the ESM output directory.

Changes:

  • Removed the generateBundle plugin that wrote dist/esm/package.json with { "type": "module" }.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 13, 2026

Greptile Summary

This PR removes the stackframe: write esm package.json Rolldown plugin from the shared tsdown build config. That plugin was responsible for emitting a dist/esm/package.json file with {"type":"module"} to signal to Node.js that the .js files in the ESM output directory are ES modules. The stated motivation is to fix an infinite loop in useStackApp caused by the classic React "dual-package hazard": when both dist/esm/ (ESM) and dist/ (CJS) copies of the package are resident in memory simultaneously, each has its own copy of every module-level singleton — including React.createContext results — so a StackProvider mounted from one copy is invisible to useStackApp reading from the other copy.

Key observations:

  • The removed plugin was introduced in PR Local emulator base #1233 ("Local emulator base") and the change here effectively reverts it.
  • dist/esm/*.js files still contain true ES module syntax (import/export) produced by tsdown's format.esm step. Removing the {"type":"module"} marker does not remove the ESM output; it only removes the signal that tells Node.js native ESM loader how to interpret those .js files.
  • Without that marker, any caller that reaches dist/esm/*.js through Node.js's native ESM loader (rather than through a bundler such as webpack/SWC) will encounter a SyntaxError at runtime.
  • For Next.js / webpack / Vite consumers (the primary audience), bundlers parse file content directly and are generally unaffected by the package.json type field, so the day-to-day developer experience may be unchanged — but the true dual-module root cause is also unaddressed in that environment.
  • A more robust fix would be a globalThis-based singleton guard for the React context objects, renaming ESM output to .mjs, or dropping the dual build entirely.

Confidence Score: 2/5

  • Not safe to merge without further validation — the fix may introduce a runtime SyntaxError for Node.js-native ESM consumers and doesn't address the root dual-module cause for bundled environments.
  • The change removes a build-time plugin that marks dist/esm/ files as ES modules. The dist/esm/*.js output still contains ES module syntax, so removing the {"type":"module"} marker breaks the contract for Node.js-native ESM loaders. Additionally, the true root cause (dual-module hazard in React context) is not resolved for bundled environments like Next.js/webpack. The change was an unintended side-effect introduced in PR Local emulator base #1233 and this revert may have subtle consequences that need more investigation.
  • configs/tsdown/js-library.ts — the only changed file, but its effect propagates to every package built with this shared config (at least @stackframe/stack and @stackframe/stack-shared).

Important Files Changed

Filename Overview
configs/tsdown/js-library.ts Removes the stackframe: write esm package.json plugin that emitted {"type":"module"} into dist/esm/package.json. Without this marker, Node.js native ESM loader will try to load dist/esm/*.js files (which contain ES module export syntax) as CommonJS, causing SyntaxError. The change reverts to the state before PR #1233 introduced the plugin.

Sequence Diagram

sequenceDiagram
    participant App as Next.js App
    participant BundlerESM as Bundler (import condition)
    participant BundlerCJS as Bundler (require condition)
    participant ESMBuild as dist/esm/index.js<br/>(ES module syntax)
    participant CJSBuild as dist/index.js<br/>(CommonJS)
    participant NodeESM as Node.js ESM Loader<br/>(no bundler)

    Note over App: With plugin (before this PR)
    App->>BundlerESM: import "@stackframe/stack"
    BundlerESM->>ESMBuild: resolves via "import" condition
    Note over ESMBuild: dist/esm/package.json<br/>{"type":"module"} present
    ESMBuild-->>App: ESM module instance A (own React context)

    App->>BundlerCJS: require("@stackframe/stack")
    BundlerCJS->>CJSBuild: resolves via "require" condition
    CJSBuild-->>App: CJS module instance B (different React context)
    Note over App: ⚠️ Dual-module hazard: useStackApp from B<br/>cannot see StackProvider from A → infinite loop

    Note over App: Without plugin (after this PR)
    App->>BundlerESM: import "@stackframe/stack"
    BundlerESM->>ESMBuild: still resolves to dist/esm/index.js
    Note over ESMBuild: No dist/esm/package.json<br/>Bundler parses ESM content directly → OK
    ESMBuild-->>App: bundler handles it, but dual-module hazard may persist

    App->>NodeESM: import "@stackframe/stack" (no bundler)
    NodeESM->>ESMBuild: resolves via "default" condition
    Note over ESMBuild: No {"type":"module"} → Node.js treats as CJS
    ESMBuild-->>NodeESM: ❌ SyntaxError: unexpected export keyword
Loading

Last reviewed commit: 96dfbab

Comment on lines 75 to 79
external: true,
};
},
},
{
name: 'stackframe: write esm package.json',
generateBundle(options) {
if (options.dir?.endsWith('/esm') || options.dir === 'dist/esm') {
this.emitFile({
type: 'asset',
fileName: 'package.json',
source: JSON.stringify({ type: 'module' }) + '\n',
});
}
},
},
}
],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing the ESM package.json risks breaking Node.js-native ESM consumers

The removed plugin wrote {"type":"module"} into dist/esm/package.json at build time. That file is still listed as the ESM entry point in the package exports map:

// packages/stack/package.json
"import": { "default": "./dist/esm/index.js" }

// packages/stack-shared/package.json
"default": "./dist/esm/index.js"

The dist/esm/*.js files still contain ES module export/import syntax (produced by tsdown's format.esm). Without the {"type":"module"} marker, Node.js's native ESM loader will attempt to parse them as CommonJS and throw a SyntaxError: Cannot use import statement in a module (or similar) for any caller that reaches dist/esm/ files without an intermediate bundler — for example, Node.js scripts that do import "@stackframe/stack" directly, or test runners that load the package in Node.js native ESM mode.

This also doesn't address the true root cause of the React context loss: the classic dual-package hazard where both dist/esm/ and dist/ are in memory simultaneously with separate module-level singletons (including separate React.createContext instances). A more targeted fix would be:

  • Deduplicating the context object outside the dual build (e.g., with globalThis-based singleton fallback), OR
  • Dropping the dual-build in favour of a single CJS build (as the previous tsup config did), OR
  • Changing the dist/esm/*.js file extension to .mjs to make the module format unambiguous without a directory-level package.json.

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.

3 participants