0

I am making a SSR react contentful application and after I hydrate the app correctly I am getting an error related to the data I got correctly from the server. It thinks the key I am passing to the contentful API does not exist but it did for the server request. I can't quite figure out why it would throw this error this way. Has anyone run into this the code is here minus the keys for obvious reasons.

https://github.com/JoshBowdenConcepts/goop-troop-collective

the current error looks like this:

Uncaught TypeError: Expected parameter accessToken
    K contentful.js:53
    267 client.js:8
    l (index):1
    100 main.27827bac.chunk.js:1
    l (index):1
    r (index):1
    t (index):1
    <anonymous> main.27827bac.chunk.js:1
contentful.js:53:10

Cheers,

Josh Bowden
  • 892
  • 3
  • 12
  • 28
  • 1
    Could you include a [minimal, reproducible example of the error](https://stackoverflow.com/help/minimal-reproducible-example)? Or at least the relevant errors and pieces of code? As it stands, the question is not exactly in a Q&A format which StackOverflow is best suited for. The Help center has some tips that you might be interested in: [How do I ask a good question?](https://stackoverflow.com/help/how-to-ask) – cbr Oct 13 '20 at 14:13
  • How would you go about that without adding private keys because as the question stands you can't make it reproducable without them and I can't provide them. – Josh Bowden Oct 13 '20 at 15:16
  • Is that the whole stack trace? Do I understand correctly, that this error occurrs on the client side? – cbr Oct 13 '20 at 15:27
  • Yes that is and maybe that in itself is the issue because the client side doens't have access to the config variables? – Josh Bowden Oct 13 '20 at 15:32
  • There is the full console log. I am wondering how I would get this info on the client correctly – Josh Bowden Oct 13 '20 at 15:35

1 Answers1

3

The problem is that while the server-side rendered tree passes siteInfo to <App />, the clientside bundle's index.js does not. If you were running a development build of React, you'd probably see errors related to the hydrated tree differing from the DOM. You'll need to pass the initial props to the client somehow - one popular trick is to inject them into a global variable and pass that, for example:

Server:

getSiteInformation().then((info) => {
  const siteInfoInjector = `<script>window.__INITIAL_PROPS = ${JSON.stringify(
    info.fields
  )};</script>`;

  return res.send(
    data.replace(
      '<div id="root"></div>',
      `${siteInfoInjector}
      <div id="root">
      ${ReactDOMServer.renderToString(
        <App siteInfo={info.fields} />
      )}
      </div>`
    )
  );
});

Client's index.js:

const initialProps = window.__INITIAL_PROPS;

ReactDOM.hydrate(
  <React.StrictMode>
    <App siteInfo={initialProps} />
  </React.StrictMode>,
  document.getElementById("root")
);

A word of warning though, this injects the stringified result directly into the HTML, meaning that if your siteTitle is hello </script><script>alert(1), you've got an XSS on your hands. One way you could fix this is by base-64 encoding the initial value:

Server:

const siteInfoInjector = `<script>window.__INITIAL_PROPS = "${Buffer.from(
  JSON.stringify(info.fields)
).toString("base64")}";</script>`;

Client:

const initialProps = JSON.parse(atob(window.__INITIAL_PROPS));
cbr
  • 12,563
  • 3
  • 38
  • 63
  • An XSS is probably not that easy to pull off in a `JSON.stringify`, it will usually escape `/` slashes to break any closing tags (which is only an issue in older browsers) – apokryfos Oct 13 '20 at 16:30
  • @apokryfos Do you mean backwards slashes? I gave it a test and the XSS is there for sure - for example `{siteName: 'hello '}` becomes `window.__INITIAL_PROPS = {"siteName":"hello "}` and even though the `` is inside a string in javascript, the HTML parser terminates the ` – cbr Oct 13 '20 at 16:35
  • `{siteName: 'hello '}` is not what `JSON.stringify` will produce though, it's not valid JSON, and the HTML parser won't terminate the script tag within a string. – apokryfos Oct 13 '20 at 16:39
  • @apokryfos Sorry, I meant that with the value of `info.fields` being the former, the latter is what gets inserted into `siteInfoInjector`. Unfortunately the HTML parser absolutely will terminate the script tag even if it is within JS string. See [this question](https://stackoverflow.com/q/236073/996081) for example. You can also try it out on JSFiddle or even in your browser's devtools' DOM inspector - try editing some node on the page as HTML and give it a go. – cbr Oct 13 '20 at 16:43
  • Strange I thought JSON.stringify escaped slashes. It doesn't seem to do so. I thought `` was JSON encoded to `<\/script>`. I must be thinking of something else – apokryfos Oct 13 '20 at 16:47
  • @cbr so I am going through this to test where did you put the siteInfoInjector? I haven't tried to run scripts in the start of an app like this. Would you declare it after the app component? – Josh Bowden Oct 13 '20 at 17:42
  • @JoshBowden The `siteInfoInjector` is just a helper variable over at the server's side. The data goes to a global variable, namely `window.__INITIAL_PROPS`. The server essentially injects a ` – cbr Oct 13 '20 at 17:45
  • @cbr ok that makes sense and I have that setup correctly. I am still running into an error getting the accessToken. I am not sure why this is still persisting – Josh Bowden Oct 13 '20 at 17:50
  • @JoshBowden Have you updated the Github repository with the latest code? – cbr Oct 13 '20 at 17:55
  • @cbr just pushed the changes – Josh Bowden Oct 13 '20 at 17:58
  • @JoshBowden Did you remember to rebuild your client and restart the server afterwards? Have you tried changing `serviceWorker.register()` to `serviceWorker.unregister()`? – cbr Oct 13 '20 at 18:14
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/222980/discussion-between-cbr-and-josh-bowden). – cbr Oct 13 '20 at 18:14