Meteor SSR 3rd Attempt

Talk about becoming deflated. Meteor’s documentation makes this appear very simple, yet I just can’t figure out what I’m getting so very wrong here. My understanding, based on all I’ve read and the help I’ve received so far is that meteor will render static ssr routes first then inject the client-side routes after the initial load. This means you should have server routes and client routes.

Now, I’ve basically copied the documentation along with some suggestions I’ve found, but I just get errors, soooooo many errors. The current error is:

Warning: React.createElement: type is invalid – expected a string (for built-in components) or a class/function (for composite components) but got: <Switch />. Did you accidentally export a JSX literal instead of a component?

I apologise to everyone that has tried to help so far. I’m just not getting this and I really need to.

Path: client/main.html

<head>
</head>

<body>
  <div id="react-target"></div>
</body>

Path: startup/client.js

onPageLoad(async sink => {
  const App = (await import('../ui/layouts/App.jsx')).default;
  ReactDOM.hydrate(
    <BrowserRouter>
      <App />
    </BrowserRouter>,
    document.getElementById('app')
  );
});

Path: startup/client-routes.js

const ClientPage = () => <h1>Client index.</h1>;

export default (
  <Switch>
    <Route path="/ClientPage" component={ClientPage} />
  </Switch>
);

Path: startup/server.js

import App from './server-routes';

onPageLoad(sink => {
  sink.renderIntoElementById(
    'app',
    renderToString(
      <StaticRouter location={sink.request.url}>
        <App />
      </StaticRouter>
    )
  );
});

Path: startup/server-routes.js

const ServerPage = () => <h1>Server index.</h1>;

export default (
  <Switch>
    <Route path="/ServerPage" component={ServerPage} />
  </Switch>
);

Path: imports/ui/layouts/App.jsx

const App = props => {
    return <Router />;
};

Looks like you’re trying to render into an element with the id app when the html doesn’t contain one:

// js
  sink.renderIntoElementById(
    'app',
// html
  <div id="react-target"></div>

So easiest is to change the id="react-target" with id="app"

Also looks like in startup/client-routes.js and startup/server-routes.js you are exporting jsx rather than exporting a function that returns jsx, which is the cause of the error you’re getting

Hey @coagmano, thanks for your help again. Still getting the same error. Warning: Failed prop type: The prop context is marked as required in StaticRouter, but its value is undefined in StaticRouter

Path: startup/client-routes.js

const ClientPage = () => <h1>Client index.</h1>;

export default function myClientRoutes() {
  return (
    <Switch>
      <Route path="/ClientPage" component={ClientPage} />
    </Switch>
  );
}

Path: startup/server-routes.js

const ServerPage = () => <h1>Server index.</h1>;

export default function myServerRoutes() {
  return (
    <Switch>
      <Route path="/ServerPage" component={ServerPage} />
    </Switch>
  );
}

Hey @minhna what the… Trying to get this to work here. Not sure why this has been flagged.

Can you post it up on github so I can take a better look?

https://bitbucket.org/bp01/meteorssrstarterpack/src/6e0c047934727250592405ad14f1264015a91211/?at=ssrV2

@coagmano is that want you wanted?

Sorry @coagmano, make sure you grab the ssrV2 not master.

Running the code you’ve linked to works fine.
You have one server rendered route specified: /ServerPage, going to that URL renders out the expected output <h1>Server index.</h1>.
You don’t have any client side code set up, so nothing happens on any other page.

This message:
Warning: Failed prop type: The prop context is marked as required in StaticRouter, but its value is undefined in StaticRouter
is just a warning, not an error.

So lets look at why StaticRouter wants context: https://reacttraining.com/react-router/web/api/StaticRouter/context-object
Which links here for a more in depth explaination: https://reacttraining.com/react-router/web/guides/server-rendering/

Which, in short, allows components to set bits of information for you to use before finalising the response.
The example they give is using <Redirect> components to set the correct status code and the new location in http headers.

As for the client side, you’ll want the server rendered routes to correspond with matching client side routes, so that when React starts up on the client to re-hydrate it all matches up correctly

Ahh, ok.

When you say I don’t have any client side code set up, so nothing happens on any other page, I’m a little confused. Within startup there’s client.js and client-routes.js. How do I connect the client side code?

You have a single client entry point set up in package.json:

  "meteor": {
    "mainModule": {
      "client": "client/main.jsx",
      "server": "server/main.js"
    }
  },

That points to this file: https://bitbucket.org/bp01/meteorssrstarterpack/src/6e0c047934727250592405ad14f1264015a91211/client/main.jsx?at=ssrV2
Which is completely commented out.

To load and run startup/client.js you need to import it from your entrypoint.

import '../imports/startup/client';

After which, you have one client route set up, /ClientPage.
Going to that route renders <h1>Client index.</h1>, so it’s working as well

You also get the warning Warning: Expected server HTML to contain a matching <h1> in <div>.
Note that it’s just a warning and the page still renders.

This warning comes because you are telling React to rehydrate the page, but there is no static html to rehydrate. You want to rehydrate pages that were server-rendered and plain old ReactDOM.render when it’s purely client rendered

@coagmano Dude, this is starting to work. Thanks. The problem I’m getting now, outside the warning, is the serverpage loads appears on the screen and then disappears.

I’m guessing that means that you’re rendering or rehydrating a different page onto it?
Are you getting any errors or warnings?

What does your client startup code look like now?
Are the routes running the same react code?

@coagmano

Ok, I’ve got it working I think. Do I need to include all the routes in client? I’ve updated the repo below. Do you mind downloading and confirming that this is the correct way to do this?

https://bitbucket.org/bp01/meteorssrstarterpack/src/ssrV3/

The client should have every route, otherwise users won’t be able to access those pages.

Having a look at your login page, it server renders and then works properly on the client as well

In my app I have a set of routes I allow to render on the server, and a bunch I specifically don’t allow. Basically, authenticated routes don’t get rendered on the server. I also do code splitting, and some routing is done on the other side of a split (like the “manage” section of the app, which requires authentication).

1 Like

@captainn and @coagmano

Thanks for your help boys. I’m using SCSS to manage styles. Specifically, I’m using the fourseven:scss package.

When I try to load styles into the SSR rendered pages, the server crashes Error: Cannot find module. The file is there, I’ve checked the path multiple times. I’ve also tried using a CSS file and it produces the same type of error.

What am I missing here?

Path: imports/startup/server.js

import React from 'react';
import { renderToString } from 'react-dom/server';
import { onPageLoad } from 'meteor/server-render';
import { StaticRouter } from 'react-router-dom';
import App from './server-routes';

import './server/server-styles.scss';

const context = {};
onPageLoad(sink => {
  const sheet = new ServerStyleSheet();
 
  sink.renderIntoElementById(
    'react-target',
    renderToString(
      <StaticRouter location={sink.request.url} context={context}>
        <App />
      </StaticRouter>
    )
  );
});

Path: imports/startup/server/server-styles.scss

h1 {
    font-size: 10rem;
}

It turns out the problem I was trying to solve didn’t need to be solved at all. CSS was being injected into my rendered SSR pages. The problem related to font awesome.