theatre-js / theatre

Motion design editor for the web
https://www.theatrejs.com
Apache License 2.0
10.86k stars 338 forks source link

Make Theatre studio play nicer with Next.js, SSR, and hot reload #303

Closed verekia closed 1 year ago

verekia commented 1 year ago

It is currently possible to use Theatre in a Next.js project but there are errors both in the browser console and in the Next.js terminal console due to some logic being run on the server during SSR. The app also crashes when hot reloading due to the R3F extension being already registered.

AriaMinaei commented 1 year ago

Now fixed by #298 and will be in v0.5.1. Thanks for reporting the issue :)

thiagobrez commented 1 year ago

Hi @AriaMinaei is there a release candidate or tag I could use to test this beforehand? I tried @0.5.0-insiders.b705c0b mentioned in the PR but it doesn't seem to do the trick. Thanks!

yoansj commented 1 year ago

Hello @AriaMinaei I am running the same issue as @thiagobrez and I would also love to know the release candidate or tag to use, thank you very much ! 😄

verekia commented 1 year ago

FWIW, I tried the then-lastest insider version 1 week ago, and I was still getting warnings and a new error, so I suppose it's not ready yet :)

verekia commented 1 year ago

@AriaMinaei Still getting a lot of console warnings and server-side ReferenceError: window is not defined with 0.5.1-rc.1.

0.5.1-rc.1:

event - compiled client and server successfully in 2.7s (1949 modules)
wait  - compiling /_error (client and server)...
error - ReferenceError: window is not defined
    at Object.<anonymous> (/Users/verekia/Local/Code/private-projects/node_modules/.pnpm/@theatre+studio@0.5.1-rc.1_@theatre+core@0.5.1-rc.1/node_modules/@theatre/studio/dist/index.js:52037:1)
    at Module._compile (node:internal/modules/cjs/loader:1119:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1173:10)
    at Module.load (node:internal/modules/cjs/loader:997:32)
    at Module._load (node:internal/modules/cjs/loader:838:12)
    at Module.require (node:internal/modules/cjs/loader:1021:19)
    at require (node:internal/modules/cjs/helpers:103:18)
    at Object.<anonymous> (/Users/verekia/Local/Code/private-projects/node_modules/.pnpm/@theatre+r3f@0.5.1-rc.1_bxw727elgbdmrpxfkxx5mvjf74/node_modules/@theatre/r3f/dist/extension/index.js:18471:33)
    at Module._compile (node:internal/modules/cjs/loader:1119:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1173:10) {
  page: '/games/aoe'
}
You seem to have imported '@theatre/studio' but haven't initialized it. You can initialize the studio by:

import studio from '@theatre/studio'
studio.initialize()

* If you didn't mean to import '@theatre/studio', this means that your bundler is not tree-shaking it. This is most likely a bundler misconfiguration.

* If you meant to import '@theatre/studio' without showing its UI, you can do that by running:

import studio from '@theatre/studio'
studio.initialize()
studio.ui.hide()

With 0.5.0 :

wait  - compiling /games/aoe (client and server)...
event - compiled client and server successfully in 2.1s (1949 modules)
@theatre/dataverse is running in a server rather than in a browser. We haven't gotten around to testing server-side rendering, so if something is working in the browser but not on the server, please file a bug: https://github.com/theatre-js/theatre/issues/new
You seem to have imported '@theatre/studio' but haven't initialized it. You can initialize the studio by:

import studio from '@theatre/studio'
studio.initialize()

* If you didn't mean to import '@theatre/studio', this means that your bundler is not tree-shaking it. This is most likely a bundler misconfiguration.

* If you meant to import '@theatre/studio' without showing its UI, you can do that by running:

import studio from '@theatre/studio'
studio.initialize()
studio.ui.hide()

Are you doing any server-side rendering with Theatre? If not, I believe there shouldn't be any message in the console at all.

If you are doing some checks like typeof window === undefined, it will break SSR. Checks need to happen after app mount to not break SSR (in a useEffect in React for instance).

Ah yes it has been introduced by the PR mentioned above: #298

See this article: https://www.joshwcomeau.com/react/the-perils-of-rehydration/

Let's reopen the issue for now? :)

AriaMinaei commented 1 year ago

Yup, reopening the issue :) Thanks for the tip @verekia 🙏

AndrewPrifer commented 1 year ago

@verekia could you please link me to a reproduction of this issue? The article from Josh talks specifically about conditionally rendering React components based on the window check, which, breaks rehydration, but you seem to be getting a ReferenceError, which shouldn't happen. Afaict all our window usages are wrapped in typeof window === 'undefined' checks, which won't throw ReferenceErrors.

AndrewPrifer commented 1 year ago

This should be resolved in the latest prerelease.

verekia commented 1 year ago

0.5.1-rc.2 is not crashing anymore 👍

However there are still server-side logs, which in my opinion should not happen for a fully client-side tool like Theatre. But that will probably require some more significant rewriting.

Could you add to the docs the proper way to load Theatre.js in a Next environment? I wrote a quickstart Next + R3F setup here if you want a minimal setup for testing.

AriaMinaei commented 1 year ago

@verekia Making progress on that. Feel free to track #353 for updates.

AriaMinaei commented 1 year ago

Done, via #353

verekia commented 1 year ago

This is a lot better now in 0.6.0, thank you! For reference, here is my current setup to get Theatre working with no console message + excluding it from production (I only want to use it to inspect my scene in development):

A global variable to check if Theatre has been initialized in a separate file (I think this was needed to play nicer with HMR) in theatre.ts:

const theatre = { isInit: false }
export default theatre

The main setup:

import { SheetProvider, editable } from '@theatre/r3f'
import extension from '@theatre/r3f/dist/extension'
import studio from '@theatre/studio'

if (process.env.NODE_ENV === 'development' && !theatre.isInit && typeof window !== 'undefined') {
  studio.initialize()
  studio.extend(extension)
  studio.ui.hide() // Hidden by default
  theatre.isInit = true
}

const IndexPage = () => (
  theatre.isInit
    ? (
      <SheetProvider sheet={getProject('Project').sheet('Sheet')}>
        <Scene />
      </SheetProvider>
    : <Scene />
)