marko-path-router icon indicating copy to clipboard operation
marko-path-router copied to clipboard

Allow routes to be rendered server side

Open ctdio opened this issue 7 years ago • 6 comments

At the moment, the router does not allow for routes to be rendered server side. For this to be able to happen, some tweaks need to be made with how components are rendered by the router.

Also, since the input that the router would receive after a server side render would be a plain javascript object (stripped of all functions), a fresh set of routes would probably have to be passed into the router so that it can rebuild it's internal path router with proper components that can be rendered.

ctdio avatar Apr 22 '17 07:04 ctdio

:+1:

mauricionr avatar May 12 '17 05:05 mauricionr

Hi @charlieduong94,

what do you think of the following workaround?

  1. Lasso is used to render a template file on the server with express.
// server.js

app.get('/*', function(req, res) {
  res.marko(
    require('./src/components/template.marko'),
    { initialRoute: req.path }
  );
}
  1. An include-tag is used to add a specific component dynamically according to the requested path on the server. (see template.marko)
  2. in the onMount function (which gets executed on the client) the router is initialized + rendered asynchronously and then replaces the content in the dom accordingly. (see template.marko)
// src/components/template.marko

import routes from "../routes";

class {
  onMount() {
    const appComponent = this.getEl("app");

    let initialRoute = "/";
    if(window && window.location){
      initialRoute = window.location.pathname;
    }
    console.log("initialRoute", initialRoute);

    Router.render({
      routes: routes,
      initialRoute,
      },
      (err, result) => {
        result.replaceChildrenOf(appComponent);
      }
    );
}

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>test</title>
    <lasso-head/>
  </head>
  <body>
    <div no-update key="app">
      <include(getComponentByRoute(routes, input.initialRoute || "/"),
               getParamsFromUrl(routes, input.initialRoute)) />
    </div>
    <lasso-body/>
  </body>
</html>

Both getComponentByRoute and getParamsFromUrl are executed on the server.

  • getComponentByRoute gets the same route-data as the router on the client and just returns the correct component for this route.
  • getParamsFromUrl transforms the url (using route data for nested components) to get the parameters which are then injected as input into component loaded via include.

In this case the page is completely rendered on the server and the client router updates the page as soon as it's loaded.

Are there any problems I didn't see or is there a better solution?

Addition: In this example the initial route on the client contains only the path name (location.pathname), in the real world one needs to add the search + hash

libeanim avatar Oct 07 '17 06:10 libeanim

Hello @libeanim, interesting workaround! Your solution seems pretty sound to me. Have you tried to get a simple proof of concept working?

ctdio avatar Oct 08 '17 03:10 ctdio

@charlieduong94 thanks for your quick response.

I quickly set up a demo. Didn't parse any url parameters (so no getParamsFromUrl) or placeholder paths (e.g. /user/:userid) tho, but I guess it shouldn't be a problem to implement that, as it is done somewhere in the router anyways.

libeanim avatar Oct 08 '17 08:10 libeanim

👍 awesome! The only minor issue I can see happening is that components that perform animations on initial render or perform any sort of setup during their onMount phase may see some "flickering" from router component remounting on the page.

Ideally, I would like to have the router transparently rebuild it's internal state without having to perform a full rerender. Perhaps this can be done by simply exposing a way for the router to "refresh" it's routes upon the page's onMount (that's something I'll try to play around with). Other than that, I think this is still a pretty good solution for server side rendering. Nice job @libeanim

ctdio avatar Oct 08 '17 15:10 ctdio

Thanks!

Yeah you are right this is a problem, the component shouldn't be rerendered/mounted twice. Cool, I'm curious to see your final solution! Keep us in the loop :smiley:

libeanim avatar Oct 09 '17 03:10 libeanim