Open nathanchase opened 6 years ago
@Alexander-Taran Yes, I'm already on that thread, but rquast was going a different route (tying data statically to each route via the router config) whereas I need the data populated dynamically depending on API data. I haven't been able to find any way to do this anywhere online with Aurelia. It seems like it's no big deal in the Vue (vue-meta)/React (react-helmet) camps, so I'm trying to figure out how exactly to do it in Aurelia.
Just so I understand this correctly, you want to:
To rephrase: metadata goes from the server to the client, is assigned/distributed on the client, which the server then needs in order to pre-render a page that isn't actually loaded from the server since you're navigating to it from the client side.
I must be misinterpreting you somewhere but that's how I'm reading it. Please correct me if I'm wrong :)
@fkleuver Using SSR, running Aurelia server-side, the requested page/route is rendered with metatags that are dynamically populated by whatever route is requested.
Example: myapp.com/user/JohnDoe
Aurelia should render the user view, then takes the dynamic value "JohnDoe", sends to the API, pulls appropriate information about that page, then adds it to the <head>
.
Then it gets rendered server-side in HTML before the client-side Aurelia begins.
This is a mandatory need for my app, as I have thousands of individual pages with SEO requirements AND must be deep-linkable via OpenGraph (Facebook) and Twitter.
JSON-LD client-side isn't good enough, and even Google's JS rendering isn't good enough. Neither of those options are supported by Facebook or Twitter (or other future social share use cases).
Ah that makes sense. I guess we wouldn't need to do anything specific in aurelia-router
as this information (like any other arbitrary info) can just be added to a route config's settings
property.
Perhaps accompanied by some SSR configuration to tell SSR on which property it needs to look. Then it can't accidentally do unexpected things when people use a particular settings property for other purposes that just happens to match the (metadata
?) name we look for.
In any case not much (or nothing at all) can/should be done in aurelia-router
to accomodate this. The server must figure out what to do with the info stored in route configs and carry out the appropriate mutations. That's just how I see it though. Maybe someone else has a better idea.
@fkleuver Well, look at vue-meta's implementation:
<template>
...
</template>
<script>
export default {
metaInfo: {
title: 'My Example App', // set a title
titleTemplate: '%s - Yay!', // title is now "My Example App - Yay!"
htmlAttrs: {
lang: 'en',
amp: undefined // "amp" has no value
}
}
}
</script>
I would think you could pull the dynamic route slug off the route, use isomorphic-fetch to send it to the API to get back the metadata needed, and then that "metadata" object is injected into the head before Aurelia renders the page.
The thing is, I can't use route.config, because that would be static only.
Rquast's "transformer-step" code is close, but it's pulling from the route.config for the data, whereas I just need it to fetch from an API instead.
The aurelia-router already has a title property, so I thought it made sense that it should potentially have access to populate the entire <head>
as needed - particularly now that SSR is "shipped".
The "ssr-transformer":
import {DOM} from 'aurelia-pal';
export const TRANSFORMER_TYPES = {
page: {
type: 'meta',
key: 'name',
value: 'content',
elements: [
'description',
'keywords'
]
},
opengraph: {
type: 'meta',
key: 'property',
value: 'content',
elements: [
'og:type',
'og:image',
'og:image:height',
'og:image:width',
'og:title',
'og:site_name',
'og:description',
'og:url'
]
}
};
/**
* Mutates header elements with an attribute of au-ssr-id with data from a set of variables
*/
export default {
variables: {},
headers: {},
getElements: function(name, config) {
let transformer = TRANSFORMER_TYPES[name];
let elements = [];
for (let key of transformer.elements) {
let el = DOM.createElement(transformer.type);
el.setAttribute(transformer.key, key);
el.setAttribute(transformer.value, config[key]);
el.setAttribute('au-ssr-id', name + '.' + key);
elements.push(el);
}
return elements;
},
appendElements: function() {
let head = DOM.querySelectorAll('head');
for (let name in this.variables) {
if (this.variables.hasOwnProperty(name)) {
let elements = this.getElements(name, this.variables[name]);
for (let element of elements) {
head[0].appendChild(element);
}
}
}
},
removeElements: function() {
let head = DOM.querySelectorAll('head');
let nodes = head[0].querySelectorAll('[au-ssr-id]');
for (let node of nodes) {
DOM.removeNode(node, head[0]);
}
},
mutate: function(config) {
if (config.variables) {
this.variables = config.variables;
} else {
// take the home route variables
this.variables = config.navModel.router.routes[0].variables;
}
if (typeof window !== 'undefined') {
// remove elements each time a browser loads or route changes
this.removeElements();
}
this.appendElements();
}
};
https://gist.github.com/rquast/a9cbc0551a48d10e83b2ad899b293c77
The aurelia-router already has a title property, so I thought it made sense that it should potentially have access to populate the entire
as needed - particularly now that SSR is "shipped".
The thing is that the page title can changed via document.title
(it doesn't actually change the title
metadata tag in the HTML file).
That's a key difference that makes title
different from those other properties, which can only be changed by modifying the html. Modifying the HTML is not something that the router should do, hence this is not really the right place to be doing that. I suppose we could add a ssrProperties
property to RouteConfig
so that it's clear that it won't do anything without SSR. Does that make sense?
So, something like:
import fetch = require('isomorphic-fetch');
activate(params, routeConfig) {
fetch('//myapp.com/api/contacts/' + params.id)
.then(function(response) {
return response.json();
}).then(function(metadata) {
routeConfig.navModel.ssrProperties(data: metadata};
});
}
?
How exactly would we be able to add tags to the HTML via the ssrProperties property? How would the ssrProperties object parse the incoming data?
I really appreciate the discussion and assistance, as I know there must be many developers in the same boat with SEO requirements for their apps.
Is there a way to create a method like routeConfig.navModel.setTitle(), but for adding meta tags to the head for SSR?
Essentially, I need a way to add html meta tags server-side (description, OpenGraph, Twitter, etc.) for each route - but it needs to be dynamic and dependent entirely on API-retrieved data.
So something like (not real code, just approximate):