Khan / aphrodite

Framework-agnostic CSS-in-JS with support for server-side rendering, browser prefixing, and minimum CSS generation
5.34k stars 188 forks source link

Aphrodite / GraphQL / SSR - cannot automatically buffer without a document #326

Open 14850842 opened 6 years ago

14850842 commented 6 years ago

I have implemented a Graphql Server Side Rendered (razzle) react app. We get an intermittent error cannot automatically buffer without a document when loading the app. It seems to be an issue when apollow gets the data from the tree and tries to compile the styles at that point.

A suggested method was to call the following:

StyleSheetTestUtils.suppressStyleInjection();
await getDataFromTree(WrappedApp);
StyleSheetTestUtils.clearBufferAndResumeStyleInjection();

But running this gives the intermittent error. Have tried other various technique but have run out of options. Server get request shown below.

server.get("/*", async (req, res, next) => {
  try {
    const client = new ApolloClient({
      ssrMode: true,
      link: createHttpLink({
        uri: process.env.RAZZLE_GQL_HOST,
        fetch: fetch
      }),
      cache: new InMemoryCache()
    });

    const context = {};
    const WrappedApp = (
      <ApolloProvider client={client}>
          <StaticRouter location={req.url} context={context}>
            <App />
          </StaticRouter>
      </ApolloProvider>
    );

    await getDataFromTree(WrappedApp);

    let metaHeader = Helmet.renderStatic();

    const { html, css } = StyleSheetServer.renderStatic(() =>
      renderToStaticMarkup(WrappedApp)
    );

    if (context.url) {
      return res.redirect(context.url);
    } else {
      return res.status(200).send(
        `<!doctype html>
            <html ${metaHeader.htmlAttributes.toString()}>
              <head>
                  <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
                  <meta charset="utf-8">
                  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
                  <style data-aphrodite>${css.content}</style>
                  ${
                    assets.client.css
                      ? `<link rel="stylesheet" href="${assets.client.css}">`
                      : ""
                  }

                  ${
                    process.env.NODE_ENV === "production"
                      ? `<script src="${assets.client.js}" defer></script>`
                      : `<script src="${
                          assets.client.js
                        }" defer crossorigin></script>`
                  }

                  ${metaHeader.title.toString()}
              </head>
              <body>
                  <div id="root">${html}</div>
              </body>
              <script>
                window.__APHRODITE_STATE__=${JSON.stringify(
                  css.renderedClassNames
                )}
                window.__APOLLO_STATE__=${JSON.stringify(
                  client.cache.extract()
                )};
              </script>
          </html>`
      );
    }
  } catch (err) {
    return next(err);
  }
});
zaklampert commented 6 years ago

Same exact issue here. Any luck?

DaBs commented 6 years ago

Same issue here. This essentially makes aphrodite unusable for my current project, which is sad. I'll dig more into some render calls to see what we can do about it.

adamrneary commented 6 years ago

Same problem at Airbnb. We we're going to implement this at the interface level, swapping out a noop interface for the Aphrodite interface, but if we solve this within aphordite we could use that potentially.

DaBs commented 6 years ago

I've got a barebones version working that I will PR soon @adamrneary. I just need to update some documentation for the new methods.

EDIT: I haven't solved the hydration problem in terms of server-side rendering and classes depending upon potential state gained from tree walking with e.g. Apollo, but it does give leeway to using Aphrodite later on in your server-side server logic.

DaBs commented 6 years ago

@adamrneary @zaklampert @14850842 This PR aims to solve the basic issue. A better solution would presumably be to extend more of the existing interface to respect server-side rendering, but this is a immediate solution to the problem, assuming you don't need the data from your GraphQL queries to generate classes.