marko-path-router
marko-path-router copied to clipboard
Allow routes to be rendered server side
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.
:+1:
Hi @charlieduong94,
what do you think of the following workaround?
- 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 }
);
}
- An
include
-tag is used to add a specific component dynamically according to the requested path on the server. (seetemplate.marko
) - 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. (seetemplate.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
Hello @libeanim, interesting workaround! Your solution seems pretty sound to me. Have you tried to get a simple proof of concept working?
@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.
👍 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
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: