Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apps never render in hidden iframes in development mode #39340

Closed
1 task done
timothympace opened this issue Aug 4, 2022 · 10 comments · Fixed by #39514
Closed
1 task done

Apps never render in hidden iframes in development mode #39340

timothympace opened this issue Aug 4, 2022 · 10 comments · Fixed by #39514
Labels
bug Issue was opened via the bug report template.

Comments

@timothympace
Copy link
Contributor

Verify canary release

  • I verified that the issue exists in the latest Next.js canary release

Provide environment information

Operating System:
  Platform: darwin
  Arch: x64
  Version: Darwin Kernel Version 21.4.0: Fri Mar 18 00:46:32 PDT 2022; root:xnu-8020.101.4~15/RELEASE_ARM64_T6000
Binaries:
  Node: 14.19.1
  npm: 6.14.16
  Yarn: 1.22.19
  pnpm: N/A
Relevant packages:
  next: 12.2.3
  eslint-config-next: N/A
  react: 18.2.0
  react-dom: 18.2.0

What browser are you using? (if relevant)

Chrome 103.0.5060.134

How are you deploying your application? (if relevant)

Contentful app SDK (development)

Describe the Bug

When running NextJS in development mode (e.g. next dev), apps that are rendered into hidden cross-origin iframes can fail to render.

I first observed this issue when using NextJS to load an application via the Contentful App Framework in development mode. When Contentful loads the application, it does so by requesting a page URL via a hidden iframe. In some instances (e.g. Contentful's App Configuration screen), the app is not considered "loaded" (and thus remains hidden) until a specific Contentful SDK callback is called. The callback is typically (and specifically in my case) consumed through a React Provider, and thus is is not called until my apps first render cycle is completed.

In development mode, NextJS relies on scheduling and running an animation frame to handle flashes of unstyled content with style-loader. The problem is that hidden iframes have no guarantees about running animation frames, so while NextJS assumes requestAnimationFrame is safe to use if it's available, when used within an iframe, it's not a reliable way to schedule future work.

I believe the bug can be fixed by only relying on requestAnimationFrame if the window object is not contained within an iframe (can detect size of window too to be extra safe).

I was able to work around this bug by running this snippet of code before rendering the app:

if (typeof window !== 'undefined' && window.self !== window.top) {
  window.requestAnimationFrame = window.setTimeout;
}

Expected Behavior

Although the iframe is hidden, we should still render the app.

Link to reproduction

about:blank

To Reproduce

Apologies for no reproduction link. I couldn't easily reproduce this in other environments.

Hoping the bug description and related source code link + article is enough to illustrate the issue

@timothympace timothympace added the bug Issue was opened via the bug report template. label Aug 4, 2022
@balazsorban44
Copy link
Member

This sounds like a nieche use case, could you explain what you are trying to do? I'm just thinking that this might rather be considered a feature request than a bug. 🤔

@timothympace
Copy link
Contributor Author

I'm using a NextJS app to deploy a Contentful App.

To me it's a bug because rendering of the app depends on window.requestAnimationFrame to schedule a callback. The code itself is even okay with using setTimeout in the absence of requestAnimationFrame, but incorrectly assumes that just because requestAnimationFrame is present that it can reliably be used to schedule that callback. I don't think the behavior should depend on implementation details of requestAnimationFrame since those are not the behaviors that were being targeted.

@balazsorban44
Copy link
Member

balazsorban44 commented Aug 5, 2022

I searched for this, and the only place we reference requestAnimationFrame is here:

;(window.requestAnimationFrame || safeSetTimeout)(function () {

where we actually fall back as your workaround suggests. 🤔

Could you add a reproduction so I can test this locally myself? 🙏

@timothympace
Copy link
Contributor Author

You might have missed some important details in my bug description. I linked that line of code too, but the problem is that requestAnimationFrame is available, but just not trustworthy. In the window.requestAnimationFrame || safeSetTimeout selection, requestAnimationFrame gets selected, but the callback never fires.

You can check out this article as to why it's not firing the callback, even though requestAnimationFrame is defined and available.

@timothympace
Copy link
Contributor Author

I would love to add a reproduction, but it is a complicated setup... I can look into if that's whats necessary though

@balazsorban44
Copy link
Member

balazsorban44 commented Aug 5, 2022

Sorry, now I see! A reproduction would be necessary anyway for implementing a test if we want to fix this, so we don't regress. 👍 I'll see if I can reproduce it myself, but providing one would make it easier to fix.

@timothympace
Copy link
Contributor Author

timothympace commented Aug 5, 2022

@balazsorban44 I was able to reproduce it: https://iframe-rendering-bug.vercel.app/

Note that it's a combination of a vercel deploy and local dev server needed to reproduce. The instructions should be clear if you take a look at the link. Let me know if you have questions.

Repo is here: https://github.com/timothympace/iframe-rendering-bug

@timothympace
Copy link
Contributor Author

To summarize, the app never renders/hydrates on the client side because of this line right here:

await opts.beforeRender()
}
render(renderCtx)

It specifically awaits the promise created by the fouc checker created here:

return new Promise((resolve) => {

In a cross origin, hidden iframe, window.requestAnimationFrame will be available, but fail to run the callback.

My solution idea is to just detect this situation with window.self !== window.top and force the fouc checker to use setTimeout in that scenario. Thoughts? Happy to open a PR as well 😄

@timothympace
Copy link
Contributor Author

In fact, given that this is such a niche issue... I'd love to use it as an opportunity to open a PR and contribute. Doesn't look like a high priority bug for the vercel team to fix.

@github-actions
Copy link
Contributor

This closed issue has been automatically locked because it had no new activity for a month. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Sep 11, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug Issue was opened via the bug report template.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants