Custom Pages in Org Profile are re-mounted every minute #6663

Description

@pavel-pargachev

Preliminary Checks

Reproduction

https://github.com/pavel-pargachev/clerk-remounting-bug

Publishable key

pk_test_c3VpdGFibGUtbWFuYXRlZS00NC5jbGVyay5hY2NvdW50cy5kZXYk

Description

Steps to reproduce:

  1. Run the app in the clerk-remounting-bug repo with npm run dev
  2. Log-in/Sign-up
  3. Create Org
  4. Click "Manage" on the current org to open Org Profile popup
  5. Go to "Custom Page"
  6. Enter text in the input field
  7. Wait for 1 minute, until clerk refreshes token, and useAuth() hook fires re-render

Actual behavior:

The Custom Organization Profile Page is re-mounted every time <OrganizationSwitcher> component is re-rendered. Causing the Custom Page to lose its react state, and input's focus. (e.g. in the minimal reproduction I added <input>, which loses the entered value)

Expected behavior:

The Custom Organization Profile Page does not re-mount. And all the Custom Page state & focus is retained.

The root cause

The cause appears to be in <CustomPortalsRenderer> which internally creates jsx elements for portals recieved from props. These portals are generated in useCustomElementPortal() hook, where it create a new instance of anonimous function and returns it as a portal functional component. The React's behavior when the reference to the jsx component changes is to re-mount the whole thing. I believe to fix it useCustomElementPortal() has to return stable references in its portal output.

Related issue 2

  1. Use the same app as above
  2. Go to Org Profile popup
  3. Make browser window narrower, so that mobile layout of Org Profile popup is activated
  4. Open the Burger Menu with "General", "Members" and "Custom Page" options
  5. Close the Burget Menu
  6. Make the browser window wide again, so that desktop layout is activated

Observe: Icon next to "Custom Page" on the left side nav panel on Org Profile popup disappears

Environment

System:
OS: Windows 10 10.0.19045
CPU: (8) x64 Intel(R) Core(TM) i7-4790K CPU @ 4.00GHz
Memory: 13.55 GB / 31.89 GB
Binaries:
Node: 22.18.0 - C:\Program Files\nodejs\node.EXE
npm: 11.5.2 - C:\Program Files\nodejs\npm.CMD
Browsers:
Edge: Chromium (139.0.3405.119)
Internet Explorer: 11.0.19041.5794
npmPackages:
@clerk/nextjs: 6.31.6 => 6.31.6
@tailwindcss/postcss: ^4.1.11 => 4.1.11
@types/node: ^24.0.3 => 24.0.3
@types/react: ^19.1.8 => 19.1.8
@types/react-dom: ^19.1.6 => 19.1.6
@types/react-syntax-highlighter: ^15.5.13 => 15.5.13
clsx: ^2.1.1 => 2.1.1
next: 15.3.4 => 15.3.4
postcss: ^8.5.6 => 8.5.6
prism-react-renderer: ^2.4.1 => 2.4.1
react: ^19.1.0 => 19.1.0
react-dom: ^19.1.0 => 19.1.0
react-syntax-highlighter: ^15.6.1 => 15.6.1
tailwindcss: ^4.1.11 => 4.1.11
typescript: ^5.8.3 => 5.8.3

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triageA ticket that needs to be triaged by a team member

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
       blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
      }
      } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
      })();
      (function(){
      try {
      var __m = "github.com";
      var __re = new RegExp('^' + "github\\.com" + '
      
      Skip to content

      Custom Pages in Org Profile are re-mounted every minute #6663

      Description

      @pavel-pargachev

      Preliminary Checks

      Reproduction

      https://github.com/pavel-pargachev/clerk-remounting-bug

      Publishable key

      pk_test_c3VpdGFibGUtbWFuYXRlZS00NC5jbGVyay5hY2NvdW50cy5kZXYk

      Description

      Steps to reproduce:

      1. Run the app in the clerk-remounting-bug repo with npm run dev
      2. Log-in/Sign-up
      3. Create Org
      4. Click "Manage" on the current org to open Org Profile popup
      5. Go to "Custom Page"
      6. Enter text in the input field
      7. Wait for 1 minute, until clerk refreshes token, and useAuth() hook fires re-render

      Actual behavior:

      The Custom Organization Profile Page is re-mounted every time <OrganizationSwitcher> component is re-rendered. Causing the Custom Page to lose its react state, and input's focus. (e.g. in the minimal reproduction I added <input>, which loses the entered value)

      Expected behavior:

      The Custom Organization Profile Page does not re-mount. And all the Custom Page state & focus is retained.

      The root cause

      The cause appears to be in <CustomPortalsRenderer> which internally creates jsx elements for portals recieved from props. These portals are generated in useCustomElementPortal() hook, where it create a new instance of anonimous function and returns it as a portal functional component. The React's behavior when the reference to the jsx component changes is to re-mount the whole thing. I believe to fix it useCustomElementPortal() has to return stable references in its portal output.

      Related issue 2

      1. Use the same app as above
      2. Go to Org Profile popup
      3. Make browser window narrower, so that mobile layout of Org Profile popup is activated
      4. Open the Burger Menu with "General", "Members" and "Custom Page" options
      5. Close the Burget Menu
      6. Make the browser window wide again, so that desktop layout is activated

      Observe: Icon next to "Custom Page" on the left side nav panel on Org Profile popup disappears

      Environment

      System:
      OS: Windows 10 10.0.19045
      CPU: (8) x64 Intel(R) Core(TM) i7-4790K CPU @ 4.00GHz
      Memory: 13.55 GB / 31.89 GB
      Binaries:
      Node: 22.18.0 - C:\Program Files\nodejs\node.EXE
      npm: 11.5.2 - C:\Program Files\nodejs\npm.CMD
      Browsers:
      Edge: Chromium (139.0.3405.119)
      Internet Explorer: 11.0.19041.5794
      npmPackages:
      @clerk/nextjs: 6.31.6 => 6.31.6
      @tailwindcss/postcss: ^4.1.11 => 4.1.11
      @types/node: ^24.0.3 => 24.0.3
      @types/react: ^19.1.8 => 19.1.8
      @types/react-dom: ^19.1.6 => 19.1.6
      @types/react-syntax-highlighter: ^15.5.13 => 15.5.13
      clsx: ^2.1.1 => 2.1.1
      next: 15.3.4 => 15.3.4
      postcss: ^8.5.6 => 8.5.6
      prism-react-renderer: ^2.4.1 => 2.4.1
      react: ^19.1.0 => 19.1.0
      react-dom: ^19.1.0 => 19.1.0
      react-syntax-highlighter: ^15.6.1 => 15.6.1
      tailwindcss: ^4.1.11 => 4.1.11
      typescript: ^5.8.3 => 5.8.3

      Metadata

      Metadata

      Assignees

      No one assigned

        Labels

        needs-triageA ticket that needs to be triaged by a team member

        Type

        No type

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
          Skip to content

          Custom Pages in Org Profile are re-mounted every minute #6663

          Description

          @pavel-pargachev

          Preliminary Checks

          Reproduction

          https://github.com/pavel-pargachev/clerk-remounting-bug

          Publishable key

          pk_test_c3VpdGFibGUtbWFuYXRlZS00NC5jbGVyay5hY2NvdW50cy5kZXYk

          Description

          Steps to reproduce:

          1. Run the app in the clerk-remounting-bug repo with npm run dev
          2. Log-in/Sign-up
          3. Create Org
          4. Click "Manage" on the current org to open Org Profile popup
          5. Go to "Custom Page"
          6. Enter text in the input field
          7. Wait for 1 minute, until clerk refreshes token, and useAuth() hook fires re-render

          Actual behavior:

          The Custom Organization Profile Page is re-mounted every time <OrganizationSwitcher> component is re-rendered. Causing the Custom Page to lose its react state, and input's focus. (e.g. in the minimal reproduction I added <input>, which loses the entered value)

          Expected behavior:

          The Custom Organization Profile Page does not re-mount. And all the Custom Page state & focus is retained.

          The root cause

          The cause appears to be in <CustomPortalsRenderer> which internally creates jsx elements for portals recieved from props. These portals are generated in useCustomElementPortal() hook, where it create a new instance of anonimous function and returns it as a portal functional component. The React's behavior when the reference to the jsx component changes is to re-mount the whole thing. I believe to fix it useCustomElementPortal() has to return stable references in its portal output.

          Related issue 2

          1. Use the same app as above
          2. Go to Org Profile popup
          3. Make browser window narrower, so that mobile layout of Org Profile popup is activated
          4. Open the Burger Menu with "General", "Members" and "Custom Page" options
          5. Close the Burget Menu
          6. Make the browser window wide again, so that desktop layout is activated

          Observe: Icon next to "Custom Page" on the left side nav panel on Org Profile popup disappears

          Environment

          System:
          OS: Windows 10 10.0.19045
          CPU: (8) x64 Intel(R) Core(TM) i7-4790K CPU @ 4.00GHz
          Memory: 13.55 GB / 31.89 GB
          Binaries:
          Node: 22.18.0 - C:\Program Files\nodejs\node.EXE
          npm: 11.5.2 - C:\Program Files\nodejs\npm.CMD
          Browsers:
          Edge: Chromium (139.0.3405.119)
          Internet Explorer: 11.0.19041.5794
          npmPackages:
          @clerk/nextjs: 6.31.6 => 6.31.6
          @tailwindcss/postcss: ^4.1.11 => 4.1.11
          @types/node: ^24.0.3 => 24.0.3
          @types/react: ^19.1.8 => 19.1.8
          @types/react-dom: ^19.1.6 => 19.1.6
          @types/react-syntax-highlighter: ^15.5.13 => 15.5.13
          clsx: ^2.1.1 => 2.1.1
          next: 15.3.4 => 15.3.4
          postcss: ^8.5.6 => 8.5.6
          prism-react-renderer: ^2.4.1 => 2.4.1
          react: ^19.1.0 => 19.1.0
          react-dom: ^19.1.0 => 19.1.0
          react-syntax-highlighter: ^15.6.1 => 15.6.1
          tailwindcss: ^4.1.11 => 4.1.11
          typescript: ^5.8.3 => 5.8.3

          Metadata

          Metadata

          Assignees

          No one assigned

            Labels

            needs-triageA ticket that needs to be triaged by a team member

            Type

            No type

            Projects

            No projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              Custom Pages in Org Profile are re-mounted every minute #6663

              Description

              @pavel-pargachev

              Preliminary Checks

              Reproduction

              https://github.com/pavel-pargachev/clerk-remounting-bug

              Publishable key

              pk_test_c3VpdGFibGUtbWFuYXRlZS00NC5jbGVyay5hY2NvdW50cy5kZXYk

              Description

              Steps to reproduce:

              1. Run the app in the clerk-remounting-bug repo with npm run dev
              2. Log-in/Sign-up
              3. Create Org
              4. Click "Manage" on the current org to open Org Profile popup
              5. Go to "Custom Page"
              6. Enter text in the input field
              7. Wait for 1 minute, until clerk refreshes token, and useAuth() hook fires re-render

              Actual behavior:

              The Custom Organization Profile Page is re-mounted every time <OrganizationSwitcher> component is re-rendered. Causing the Custom Page to lose its react state, and input's focus. (e.g. in the minimal reproduction I added <input>, which loses the entered value)

              Expected behavior:

              The Custom Organization Profile Page does not re-mount. And all the Custom Page state & focus is retained.

              The root cause

              The cause appears to be in <CustomPortalsRenderer> which internally creates jsx elements for portals recieved from props. These portals are generated in useCustomElementPortal() hook, where it create a new instance of anonimous function and returns it as a portal functional component. The React's behavior when the reference to the jsx component changes is to re-mount the whole thing. I believe to fix it useCustomElementPortal() has to return stable references in its portal output.

              Related issue 2

              1. Use the same app as above
              2. Go to Org Profile popup
              3. Make browser window narrower, so that mobile layout of Org Profile popup is activated
              4. Open the Burger Menu with "General", "Members" and "Custom Page" options
              5. Close the Burget Menu
              6. Make the browser window wide again, so that desktop layout is activated

              Observe: Icon next to "Custom Page" on the left side nav panel on Org Profile popup disappears

              Environment

              System:
              OS: Windows 10 10.0.19045
              CPU: (8) x64 Intel(R) Core(TM) i7-4790K CPU @ 4.00GHz
              Memory: 13.55 GB / 31.89 GB
              Binaries:
              Node: 22.18.0 - C:\Program Files\nodejs\node.EXE
              npm: 11.5.2 - C:\Program Files\nodejs\npm.CMD
              Browsers:
              Edge: Chromium (139.0.3405.119)
              Internet Explorer: 11.0.19041.5794
              npmPackages:
              @clerk/nextjs: 6.31.6 => 6.31.6
              @tailwindcss/postcss: ^4.1.11 => 4.1.11
              @types/node: ^24.0.3 => 24.0.3
              @types/react: ^19.1.8 => 19.1.8
              @types/react-dom: ^19.1.6 => 19.1.6
              @types/react-syntax-highlighter: ^15.5.13 => 15.5.13
              clsx: ^2.1.1 => 2.1.1
              next: 15.3.4 => 15.3.4
              postcss: ^8.5.6 => 8.5.6
              prism-react-renderer: ^2.4.1 => 2.4.1
              react: ^19.1.0 => 19.1.0
              react-dom: ^19.1.0 => 19.1.0
              react-syntax-highlighter: ^15.6.1 => 15.6.1
              tailwindcss: ^4.1.11 => 4.1.11
              typescript: ^5.8.3 => 5.8.3

              Metadata

              Metadata

              Assignees

              No one assigned

                Labels

                needs-triageA ticket that needs to be triaged by a team member

                Type

                No type

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions

                  , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
                  Skip to content

                  Custom Pages in Org Profile are re-mounted every minute #6663

                  Description

                  @pavel-pargachev

                  Preliminary Checks

                  Reproduction

                  https://github.com/pavel-pargachev/clerk-remounting-bug

                  Publishable key

                  pk_test_c3VpdGFibGUtbWFuYXRlZS00NC5jbGVyay5hY2NvdW50cy5kZXYk

                  Description

                  Steps to reproduce:

                  1. Run the app in the clerk-remounting-bug repo with npm run dev
                  2. Log-in/Sign-up
                  3. Create Org
                  4. Click "Manage" on the current org to open Org Profile popup
                  5. Go to "Custom Page"
                  6. Enter text in the input field
                  7. Wait for 1 minute, until clerk refreshes token, and useAuth() hook fires re-render

                  Actual behavior:

                  The Custom Organization Profile Page is re-mounted every time <OrganizationSwitcher> component is re-rendered. Causing the Custom Page to lose its react state, and input's focus. (e.g. in the minimal reproduction I added <input>, which loses the entered value)

                  Expected behavior:

                  The Custom Organization Profile Page does not re-mount. And all the Custom Page state & focus is retained.

                  The root cause

                  The cause appears to be in <CustomPortalsRenderer> which internally creates jsx elements for portals recieved from props. These portals are generated in useCustomElementPortal() hook, where it create a new instance of anonimous function and returns it as a portal functional component. The React's behavior when the reference to the jsx component changes is to re-mount the whole thing. I believe to fix it useCustomElementPortal() has to return stable references in its portal output.

                  Related issue 2

                  1. Use the same app as above
                  2. Go to Org Profile popup
                  3. Make browser window narrower, so that mobile layout of Org Profile popup is activated
                  4. Open the Burger Menu with "General", "Members" and "Custom Page" options
                  5. Close the Burget Menu
                  6. Make the browser window wide again, so that desktop layout is activated

                  Observe: Icon next to "Custom Page" on the left side nav panel on Org Profile popup disappears

                  Environment

                  System:
                  OS: Windows 10 10.0.19045
                  CPU: (8) x64 Intel(R) Core(TM) i7-4790K CPU @ 4.00GHz
                  Memory: 13.55 GB / 31.89 GB
                  Binaries:
                  Node: 22.18.0 - C:\Program Files\nodejs\node.EXE
                  npm: 11.5.2 - C:\Program Files\nodejs\npm.CMD
                  Browsers:
                  Edge: Chromium (139.0.3405.119)
                  Internet Explorer: 11.0.19041.5794
                  npmPackages:
                  @clerk/nextjs: 6.31.6 => 6.31.6
                  @tailwindcss/postcss: ^4.1.11 => 4.1.11
                  @types/node: ^24.0.3 => 24.0.3
                  @types/react: ^19.1.8 => 19.1.8
                  @types/react-dom: ^19.1.6 => 19.1.6
                  @types/react-syntax-highlighter: ^15.5.13 => 15.5.13
                  clsx: ^2.1.1 => 2.1.1
                  next: 15.3.4 => 15.3.4
                  postcss: ^8.5.6 => 8.5.6
                  prism-react-renderer: ^2.4.1 => 2.4.1
                  react: ^19.1.0 => 19.1.0
                  react-dom: ^19.1.0 => 19.1.0
                  react-syntax-highlighter: ^15.6.1 => 15.6.1
                  tailwindcss: ^4.1.11 => 4.1.11
                  typescript: ^5.8.3 => 5.8.3

                  Metadata

                  Metadata

                  Assignees

                  No one assigned

                    Labels

                    needs-triageA ticket that needs to be triaged by a team member

                    Type

                    No type

                    Projects

                    No projects

                      Milestone

                      No milestone

                      Relationships

                      None yet

                      Development

                      No branches or pull requests

                      Issue actions

                      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                      Skip to content

                      Custom Pages in Org Profile are re-mounted every minute #6663

                      Description

                      @pavel-pargachev

                      Preliminary Checks

                      Reproduction

                      https://github.com/pavel-pargachev/clerk-remounting-bug

                      Publishable key

                      pk_test_c3VpdGFibGUtbWFuYXRlZS00NC5jbGVyay5hY2NvdW50cy5kZXYk

                      Description

                      Steps to reproduce:

                      1. Run the app in the clerk-remounting-bug repo with npm run dev
                      2. Log-in/Sign-up
                      3. Create Org
                      4. Click "Manage" on the current org to open Org Profile popup
                      5. Go to "Custom Page"
                      6. Enter text in the input field
                      7. Wait for 1 minute, until clerk refreshes token, and useAuth() hook fires re-render

                      Actual behavior:

                      The Custom Organization Profile Page is re-mounted every time <OrganizationSwitcher> component is re-rendered. Causing the Custom Page to lose its react state, and input's focus. (e.g. in the minimal reproduction I added <input>, which loses the entered value)

                      Expected behavior:

                      The Custom Organization Profile Page does not re-mount. And all the Custom Page state & focus is retained.

                      The root cause

                      The cause appears to be in <CustomPortalsRenderer> which internally creates jsx elements for portals recieved from props. These portals are generated in useCustomElementPortal() hook, where it create a new instance of anonimous function and returns it as a portal functional component. The React's behavior when the reference to the jsx component changes is to re-mount the whole thing. I believe to fix it useCustomElementPortal() has to return stable references in its portal output.

                      Related issue 2

                      1. Use the same app as above
                      2. Go to Org Profile popup
                      3. Make browser window narrower, so that mobile layout of Org Profile popup is activated
                      4. Open the Burger Menu with "General", "Members" and "Custom Page" options
                      5. Close the Burget Menu
                      6. Make the browser window wide again, so that desktop layout is activated

                      Observe: Icon next to "Custom Page" on the left side nav panel on Org Profile popup disappears

                      Environment

                      System:
                      OS: Windows 10 10.0.19045
                      CPU: (8) x64 Intel(R) Core(TM) i7-4790K CPU @ 4.00GHz
                      Memory: 13.55 GB / 31.89 GB
                      Binaries:
                      Node: 22.18.0 - C:\Program Files\nodejs\node.EXE
                      npm: 11.5.2 - C:\Program Files\nodejs\npm.CMD
                      Browsers:
                      Edge: Chromium (139.0.3405.119)
                      Internet Explorer: 11.0.19041.5794
                      npmPackages:
                      @clerk/nextjs: 6.31.6 => 6.31.6
                      @tailwindcss/postcss: ^4.1.11 => 4.1.11
                      @types/node: ^24.0.3 => 24.0.3
                      @types/react: ^19.1.8 => 19.1.8
                      @types/react-dom: ^19.1.6 => 19.1.6
                      @types/react-syntax-highlighter: ^15.5.13 => 15.5.13
                      clsx: ^2.1.1 => 2.1.1
                      next: 15.3.4 => 15.3.4
                      postcss: ^8.5.6 => 8.5.6
                      prism-react-renderer: ^2.4.1 => 2.4.1
                      react: ^19.1.0 => 19.1.0
                      react-dom: ^19.1.0 => 19.1.0
                      react-syntax-highlighter: ^15.6.1 => 15.6.1
                      tailwindcss: ^4.1.11 => 4.1.11
                      typescript: ^5.8.3 => 5.8.3

                      Metadata

                      Metadata

                      Assignees

                      No one assigned

                        Labels

                        needs-triageA ticket that needs to be triaged by a team member

                        Type

                        No type

                        Projects

                        No projects

                          Milestone

                          No milestone

                          Relationships

                          None yet

                          Development

                          No branches or pull requests

                          Issue actions

                          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                          Skip to content

                          Custom Pages in Org Profile are re-mounted every minute #6663

                          Description

                          @pavel-pargachev

                          Preliminary Checks

                          Reproduction

                          https://github.com/pavel-pargachev/clerk-remounting-bug

                          Publishable key

                          pk_test_c3VpdGFibGUtbWFuYXRlZS00NC5jbGVyay5hY2NvdW50cy5kZXYk

                          Description

                          Steps to reproduce:

                          1. Run the app in the clerk-remounting-bug repo with npm run dev
                          2. Log-in/Sign-up
                          3. Create Org
                          4. Click "Manage" on the current org to open Org Profile popup
                          5. Go to "Custom Page"
                          6. Enter text in the input field
                          7. Wait for 1 minute, until clerk refreshes token, and useAuth() hook fires re-render

                          Actual behavior:

                          The Custom Organization Profile Page is re-mounted every time <OrganizationSwitcher> component is re-rendered. Causing the Custom Page to lose its react state, and input's focus. (e.g. in the minimal reproduction I added <input>, which loses the entered value)

                          Expected behavior:

                          The Custom Organization Profile Page does not re-mount. And all the Custom Page state & focus is retained.

                          The root cause

                          The cause appears to be in <CustomPortalsRenderer> which internally creates jsx elements for portals recieved from props. These portals are generated in useCustomElementPortal() hook, where it create a new instance of anonimous function and returns it as a portal functional component. The React's behavior when the reference to the jsx component changes is to re-mount the whole thing. I believe to fix it useCustomElementPortal() has to return stable references in its portal output.

                          Related issue 2

                          1. Use the same app as above
                          2. Go to Org Profile popup
                          3. Make browser window narrower, so that mobile layout of Org Profile popup is activated
                          4. Open the Burger Menu with "General", "Members" and "Custom Page" options
                          5. Close the Burget Menu
                          6. Make the browser window wide again, so that desktop layout is activated

                          Observe: Icon next to "Custom Page" on the left side nav panel on Org Profile popup disappears

                          Environment

                          System:
                          OS: Windows 10 10.0.19045
                          CPU: (8) x64 Intel(R) Core(TM) i7-4790K CPU @ 4.00GHz
                          Memory: 13.55 GB / 31.89 GB
                          Binaries:
                          Node: 22.18.0 - C:\Program Files\nodejs\node.EXE
                          npm: 11.5.2 - C:\Program Files\nodejs\npm.CMD
                          Browsers:
                          Edge: Chromium (139.0.3405.119)
                          Internet Explorer: 11.0.19041.5794
                          npmPackages:
                          @clerk/nextjs: 6.31.6 => 6.31.6
                          @tailwindcss/postcss: ^4.1.11 => 4.1.11
                          @types/node: ^24.0.3 => 24.0.3
                          @types/react: ^19.1.8 => 19.1.8
                          @types/react-dom: ^19.1.6 => 19.1.6
                          @types/react-syntax-highlighter: ^15.5.13 => 15.5.13
                          clsx: ^2.1.1 => 2.1.1
                          next: 15.3.4 => 15.3.4
                          postcss: ^8.5.6 => 8.5.6
                          prism-react-renderer: ^2.4.1 => 2.4.1
                          react: ^19.1.0 => 19.1.0
                          react-dom: ^19.1.0 => 19.1.0
                          react-syntax-highlighter: ^15.6.1 => 15.6.1
                          tailwindcss: ^4.1.11 => 4.1.11
                          typescript: ^5.8.3 => 5.8.3

                          Metadata

                          Metadata

                          Assignees

                          No one assigned

                            Labels

                            needs-triageA ticket that needs to be triaged by a team member

                            Type

                            No type

                            Projects

                            No projects

                              Milestone

                              No milestone

                              Relationships

                              None yet

                              Development

                              No branches or pull requests

                              Issue actions

                              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                              Skip to content

                              Custom Pages in Org Profile are re-mounted every minute #6663

                              Description

                              @pavel-pargachev

                              Preliminary Checks

                              Reproduction

                              https://github.com/pavel-pargachev/clerk-remounting-bug

                              Publishable key

                              pk_test_c3VpdGFibGUtbWFuYXRlZS00NC5jbGVyay5hY2NvdW50cy5kZXYk

                              Description

                              Steps to reproduce:

                              1. Run the app in the clerk-remounting-bug repo with npm run dev
                              2. Log-in/Sign-up
                              3. Create Org
                              4. Click "Manage" on the current org to open Org Profile popup
                              5. Go to "Custom Page"
                              6. Enter text in the input field
                              7. Wait for 1 minute, until clerk refreshes token, and useAuth() hook fires re-render

                              Actual behavior:

                              The Custom Organization Profile Page is re-mounted every time <OrganizationSwitcher> component is re-rendered. Causing the Custom Page to lose its react state, and input's focus. (e.g. in the minimal reproduction I added <input>, which loses the entered value)

                              Expected behavior:

                              The Custom Organization Profile Page does not re-mount. And all the Custom Page state & focus is retained.

                              The root cause

                              The cause appears to be in <CustomPortalsRenderer> which internally creates jsx elements for portals recieved from props. These portals are generated in useCustomElementPortal() hook, where it create a new instance of anonimous function and returns it as a portal functional component. The React's behavior when the reference to the jsx component changes is to re-mount the whole thing. I believe to fix it useCustomElementPortal() has to return stable references in its portal output.

                              Related issue 2

                              1. Use the same app as above
                              2. Go to Org Profile popup
                              3. Make browser window narrower, so that mobile layout of Org Profile popup is activated
                              4. Open the Burger Menu with "General", "Members" and "Custom Page" options
                              5. Close the Burget Menu
                              6. Make the browser window wide again, so that desktop layout is activated

                              Observe: Icon next to "Custom Page" on the left side nav panel on Org Profile popup disappears

                              Environment

                              System:
                              OS: Windows 10 10.0.19045
                              CPU: (8) x64 Intel(R) Core(TM) i7-4790K CPU @ 4.00GHz
                              Memory: 13.55 GB / 31.89 GB
                              Binaries:
                              Node: 22.18.0 - C:\Program Files\nodejs\node.EXE
                              npm: 11.5.2 - C:\Program Files\nodejs\npm.CMD
                              Browsers:
                              Edge: Chromium (139.0.3405.119)
                              Internet Explorer: 11.0.19041.5794
                              npmPackages:
                              @clerk/nextjs: 6.31.6 => 6.31.6
                              @tailwindcss/postcss: ^4.1.11 => 4.1.11
                              @types/node: ^24.0.3 => 24.0.3
                              @types/react: ^19.1.8 => 19.1.8
                              @types/react-dom: ^19.1.6 => 19.1.6
                              @types/react-syntax-highlighter: ^15.5.13 => 15.5.13
                              clsx: ^2.1.1 => 2.1.1
                              next: 15.3.4 => 15.3.4
                              postcss: ^8.5.6 => 8.5.6
                              prism-react-renderer: ^2.4.1 => 2.4.1
                              react: ^19.1.0 => 19.1.0
                              react-dom: ^19.1.0 => 19.1.0
                              react-syntax-highlighter: ^15.6.1 => 15.6.1
                              tailwindcss: ^4.1.11 => 4.1.11
                              typescript: ^5.8.3 => 5.8.3

                              Metadata

                              Metadata

                              Assignees

                              No one assigned

                                Labels

                                needs-triageA ticket that needs to be triaged by a team member

                                Type

                                No type

                                Projects

                                No projects

                                  Milestone

                                  No milestone

                                  Relationships

                                  None yet

                                  Development

                                  No branches or pull requests

                                  Issue actions