[Solved] Inject HTML meta tags on the server

I am tinkering with this image storage application, and the last hurdle is to have the proper meta tags in the header so when someone sends the URL via iOS Messages it shows the proper thumbnail, but I am failing at injecting the proper <meta> tags on the server side, because

The machinery that creates link previews will not follow redirects, nor run JavaScript, so metadata must be available on the page without either occurring.Server - side redirects are followed, however, and are a good alternative.
so far I have experimented with WebApp.connectionHandlers, but whatever I inject never makes it to the client, the headers are always just what’s in the client.html

Has anybody figured out how to inject HTML headers on the server?

1 Like

I think I have been confusing HTTP headers and meta tags, which are part of the downloaded payload.

I guess what I am trying to do can’t be done without full fledged server-rendering …

1 Like

Hi @jamgold,

Im not sure if you are looking to inject a meta tag but we are doing it with server-rendersink, like the following.

import { onPageLoad } from 'meteor/server-render';


onPageLoad((sink) => {
  // You can get the url data with: sink.request.url.path ...

  sing.appendToHead(`<meta property="og:image" content="${YOUR_VALUE}"/>`);
});

Hope this helps.

2 Likes

I believe I have figured it out. With the help of the server-render package. I added this to the server side, and voila

import { onPageLoad } from "meteor/server-render";

onPageLoad(sink => {
  // parse the request URL
  const path = sink.request.url.path.split('/');
  if(path.length>2 && path[1] == 'image') {
    // only  if we request an image, get the image id
    const id = path[2];
    if (id) {
      const img = DBImages.findOne(id);
      if (img) {
        // append the appropriate meta tags
        sink.appendToHead(` <meta property="og:title" content="${img.name}"/>\n`);
        sink.appendToHead(` <meta property="og:type" content="${img.type}" />\n`);
        sink.appendToHead(` <meta property="og:url" content="https://images.buzzledom.com/image/${id}" />\n`);
        sink.appendToHead(` <meta property="og:image" content="https://images.buzzledom.com/thumbnail/${id}" />\n`);
      }
    }
  }
});

And this is with Blaze

1 Like

@pmogollon that totally (would have) helped, but I figured it out. Our posts properly overlapped :slight_smile:

1 Like

We just went thru this exact issue and ended up using an alternate solution - if you’re interested, we used prerender.io (you can even self host it on the same server if you want). We set it up on it’s own small server so any other future app we build can just re-use it quite easily. It’s nice because it automatically takes care of checking if the request is coming from a crawler type bot (iOS, facebook, whatsapp, etc etc). Happy to post more info/example config (including with redis for caching) if wanted!

Thank you @adammoisa for reminding me of prerender.io. I used to have it setup for an older, much larger project and back then I didn’t understand neither Meteor, nor prerender well enough, so “some things that should not have been forgotten were lost. History became legend. Legend became myth…” you know the rest.

Since this topic started out with a misguided use of WebApp.connectionHandlers, I feel the need to re-habilitate this most awesome feature of the stack by linking to this post which explains how I used it to dynamically serve static files.

Quick question about the server-render package. In your example @jamgold, in onPageLoad() you do a

DBImages.findOne(id);

in order to check to inject meta tags. I want to do something similar, but I’m finding this to be duplicating what my client route is going to do. That is, it does a Meteor.call() to look up the same data to pass into the Blaze template. My data is a bit larger and more involved.

So doing the lookup in the onPageLoad() is doing the same server/Mongo work twice. Is there a way to have the data in onPageLoad() passed back to the client to prevent having to do the server call for data from the client?

None of the package methods seem to support this, unless hacking it into the DOM somehow.

As a quick aside, we switched off Galaxy where with the mdg:seo package it automatically ran prerender.io for you. The only reason we need prerender.io is to, like you, have links to our app display proper thumbnails and text when pasting into Facebook, Twitter, etc. We don’t need any of the SEO benefits, just the pre-rendering of the meta tags currently set in the client using packages like lookback:seo which don’t work without pre-rendering. So looking to switch to server-render.

Yes. Explicitly add the data together as a javascript variable inside a <script> tag that can be read in the client when the client initializes. Check if the variable doesn’t exist before querying.

At the very least, set a javascript nonce boolean variable in the head which you can check before querying in client.

1 Like

@rjdavid Clever. That might just work for my method-based non-pub/sub use-case.

On the contrary, it wouldn’t probably solve the SEO use-case as it will still require JavaScript running to populate fields on the page (e.g. titles, subtitles, etc.) But SEO isn’t my use-case.

I’ll give it a shot and report back how it works.

You already used that data for SSR to generate your HTML. That will not change. You are just adding javascript to easily check if you need to query or not to remove your redundant calls.

1 Like