Google Core update and the very bad impact on meteor apps (FCP/LCP/CLS) - Desperate call for help

@nschwarz

If I would start a public repo with a boilerplate for Meteor / React / SSR with actual stack (updated libraries, hooks, styled components), would you be in to help me on it?

my time for side project is limited (whisky-hamster I mention on this post is already a side project) But you see to know what you’re doing and I am motivated to switch to ssr in the first months of 2021 so could be good match to set is up and keep it public for others to enjoy.

Yes ! No issue at all, a boiler plate is a good idea !

Not sure how well this will fly here on the forums… :eyes: but from first glance I’d question if Meteor is the best choice of stack for this. Of course, I do not know the details of your project, but it seems to be a largely static ecommerce site. For something like this I would personally opt for something like Gatsby or Next.js, which give you static rendering out of the box.

Here’s a store I built with Next.js on top of the Shopify API. Note the speed (it’s actually even faster than it looks, because of the page transition animations), page loads are near instant. The Lighthouse score is almost 100 on all fronts, only thing dragging it down are the external scripts (Klaviyo, Facebook, etc).

I love Meteor but I also think every platform has its use case. Static sites is not where it shines.

1 Like

I kinda agree with you actually and you’re in full right to say so. I’m considering alternatives but always supported Meteor and think it could also work with it as well.

I intend to check a NextJs/Mongo solution to replace it as well but first would like to considered the Meteor possibilities.

I’m using the same stack as your for an e-commerce website and it’s been a wonder. Next is great but it has its limitation !

If you want a more flexible option, you could do Next for the front and Meteor as a GraphQL headless API.

actually there is : https://docs.meteor.com/packages/server-render.html

1 Like

I started working on the boilerplate, wanna try some stuff before I put it live and then ask for opinions / advice. I’m optimistic I can stay with Meteor.

1 Like

@macrozone @nschwarz

That’s as far as I could go so far with starting from the apollo skeleton and tried to build on top of it. Right now it’s not working, but it’s something :smiley:

I don’t think the link in the ssr file is good, found an alternative online on a repo but seems big, so wanted to have your opinion if that’s any good:

   const client = new ApolloClient({
     // simple local interface to query graphql directly
     link: new ApolloLink(({ query, variables, operationName }) => {
       return new Observable(obs => {
         graphql(schema, print(query), {}, {}, variables, operationName)
           .then(result => {
             obs.next(result);
             obs.complete();
           })
           .catch(obs.error.bind(obs));
       });
     }),
     cache: new InMemoryCache(),
     ssrMode: true,
   });

I know I’m also missing the last piece, the rendering part which should look like something:

 await getDataFromTree(App);

  sink.renderIntoElementById("app", html);
  sink.appendToBody(`
    <script>
      window.__APOLLO_STATE__=${JSON.stringify(client.cache.extract())};
      window.__CSS__=${JSON.stringify(ids)}
    </script>
  `);

But here as well I’m unsure where to get the html from.

Also I still would like to include react helmet and styled-components, but that’s gonna be later, first I want the original skeleton to work.

@ivo, I’ve been looking through your code and what I can tell is :

As an advice :
instead of creating both server and client config and App you should do everything as an isomorphic import (both compatible with the server and the client), it will make it easier to debug and understand.

on your client :

  • you should use ReactDOM.hydrate not ReactDOM.render in main.jsx
  • you should set ssr: false and ssrForceFetchDelay: value (value should be at least 100) in your ApolloClient config.
  • why do you use BatchHttpLink instead of createHttpLink ?

on your server :

You’re using getDataFromTree wrong : as the doc says it returns a promise so you should use it like this :

getDataFromTree(App).then(html => {
  sink.appendToHead(`
  <script>
      window.__APOLLO_STATE__=${JSON.stringify(client.cache.extract()).replace(/</g, '\\u003c')}
      window.__CSS__=${JSON.stringify(ids)}
    </script>
  `)
  sink.renderIntoElementById("app", html)
})

as the doc says getDataFromTree render static Markup (I don’t think you want that).
To understand the difference you should read :

If static Markup is not what you want you should use renderToStringWithData which works the same way as getDataFromTree.

Hi @nschwarz,

Thanks for the feedback. I tried to implement them but getting a bit confused how to fully handle the isomorphic part. Where should what be rendered. It’s not working right now still not having anything render from the server.

Also not sure where to get the ids from this part:

window.__CSS__=${JSON.stringify(ids)}

in your getDataFromTee app for the styling? Also should window.CSS and window.APOLLO_STATE be initiated somewhere on client ?

No error on server side but getting this on client side:

Warning: Expected server HTML to contain a matching <p> in <div>.
    in p (created by Info)
    in Info (created by Context.Consumer)
    in Route (created by Routes)
    in Routes
    in Router (created by BrowserRouter)
    in BrowserRouter
    in ApolloProvider

Updated repo is here:

@ivo,

I made a pull request on the repo with the needed patch to make SSR working with apollo.

As of window.__CSS__=${JSON.stringify(ids)} I don’t understand what you want to make with this.
If you want to pass the css created through styled components, please read the doc here : https://styled-components.com/docs/advanced#server-side-rendering

Initializing the apollo cache can sometimes be necessary if you have locale states to manage. for now you should be good.

Thanks a lot, I had a few mistakes here and there and I really appreciate you checking it up and clarifying the code.

I implemented the styled component and it seems to be working. I tried to implement Helmet (using react-helmet-async as you adviced) which I think is the last step before “releasing” our repo, but somehow I’m getting an empty title through the ssr. I updated the repo and would appreciate if you could tell me what you think is the issue, The SSR code is now:

import { onPageLoad } from "meteor/server-render";
import React from "react";
import { renderToString } from "react-dom/server";
import { getMarkupFromTree } from "@apollo/client/react/ssr";
import App from "../imports/both/App";
import apolloClient from "../imports/both/apolloClient";
import { ServerStyleSheet } from "styled-components";
import { Helmet } from "react-helmet";

onPageLoad(async (sink) => {
  const sheet = new ServerStyleSheet();
  const client = apolloClient;
  const tree = sheet.collectStyles(
    <App client={client} location={sink.request.url} />
  );

  return getMarkupFromTree({
    tree,
    context: {},
    renderFunction: renderToString,
  }).then((html) => {
    sink.renderIntoElementById("app", html);

    sink.appendToHead(sheet.getStyleTags());

    const helmet = Helmet.renderStatic();
    sink.appendToHead(helmet.meta.toString());
    sink.appendToHead(helmet.title.toString());

    sink.appendToHead(`
    <script>
    window.__APOLLO_STATE__=${JSON.stringify(client.cache.extract()).replace(
      /</g,
      "\\u003c"
    )}
      </script>
      `);
  });
});

and I added this in App in the both folder:

import React from "react";
import { ApolloProvider } from "@apollo/client";
import Routes from "./../ui/Routes";
import Router from "./Router";
import { HelmetProvider } from "react-helmet-async";

export default function App({ client, location }) {
  return (
    <ApolloProvider client={client}>
      <HelmetProvider>
        <Router location={location}>
          <Routes />
        </Router>
      </HelmetProvider>
    </ApolloProvider>
  );
}

In the Info file I have basic Helmet title and meta description tag and they’re only visible on client-side. On server I get this for title and nothing for description:

<title data-react-helmet="true"></title>

It was in your previous post that’s why I ask but it seems not needed, I think you copied one of my line of code somewhere, so nevermind :).

Thanks a lot again, I think we’re very close.

@ivo,

you’re using the wrong helmet library:

use import { Helmet } from "react-helmet-async"
not import { Helmet } from "react-helmet"

please read the documentation of react-helmet-async, you’re currently using the react-helmet api.

window.CSS=${JSON.stringify(ids)}

was in your post… : Google Core update and the very bad impact on meteor apps (FCP/LCP/CLS) - Desperate call for help - #34 by ivo

@nschwarz

Yes my bad, I copied the text before changing the library…

Anyway everything seems to be working so far:

You mentioned Initializing the Apollo Cache, is it something hard to do? Could it be worth it to add in the repo too? Not sure in which case it’s needed and what’s the implication, but if it’s not too complicated and doesn’t have any possible “bad” impact I think it could be great to add as well, if you have the relevant doc for our case or a small code example somewhere I can add it too.

Otherwise I think we’re good to go. Tell me if you agree too or if you see anything to be modify?

I made a PR on the repo.

If you want to look at local state managment : https://www.apollographql.com/docs/react/local-state/managing-state-with-field-policies/

Thanks for the cleanup. As you moved the HelmetProvider to the App component I removed it from the main.jsx (I shouldn’t have put it there in the first place but it worked :D)

I think it’s ready to be posted to the public, just have to work on a small readme, I guess I’ll create a post for that afterwards if that’s ok with you ?

I’ll see if I get the time to work on the local state management even though I guess it’s a rarer use case.

@ivo,

Yes, you can make an announcement.
Local state management is not rare and quite useful, you can use it to sort, filter data without round trips to the server, you can also save client only data such as a cart, credentials, user preferences, …

But it’s not necessary to include it in the boilerplate because :

  • the documentation is quite straight forward
  • it’s not the main purpose of the boilerplate

Gotta say I never used them within Apollo, generally used other libraries (react-easy-state for example) for local state.

is it a full alternative to other state management library (redux, easy state, mobx…?)

@ivo,

yes, it as the same functionality while using graphql, and all apollo functionalities.
In my opinion, it’s much more simpler to understand and to use than redux and mobx.

1 Like

Thanks @nschwarz will have a look into it.

EDIT: Found the issue, the getStyleTags had to be part of the getMarkupTree function, otherwise can’t get the style if it’s not rendered to string yet. I think the last update is working now.


NOT RELEVANT ANYMORE BUT FOR INFO:

I just have a last issue with our repo. Somehow the style doesn’t seem to be rendering on server side anymore since the last cleanup but I ain’t sure why. Any idea ?