Twitter and many other sites are still using crawlers not adapted to single page applications.
That means, if you are changing the meta tags of a single page dynamically, crawlers will only get the meta tags of the first render.
For example, index.html
in React looks like:
html<!DOCTYPE html> <html lang="en" data-theme='🔵'> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="theme-color" content="#000000"> <meta name="description" content="I am Erik, an engineer from Barcelona. I code and share what I am learning."> <meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:site" content="@ErikMarJor" /> <meta name="twitter:title" content="TWITTER_DYNAMIC_TITLE" /> <meta name="twitter:description" content="TWITTER_DYNAMIC_DESC"/> <meta name="twitter:image" content="https://erikmartinjordan.com/TwitterCard.png" /> <link rel="manifest" href="%PUBLIC_URL%/manifest.json"> <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"> <title>Erik Martín Jordán</title> </head> <body> <noscript> You need to enable JavaScript to run this app. </noscript> <div id="root"></div> </body> </html>
Every time Twitter crawls the site, no matter the route, meta tags will remain constant. Even if you change them dynamically using JavaScript.
Conclusion: meta tags as content="TWITTER_DYNAMIC_TITLE"
and content="TWITTER_DYNAMIC_DESC"
need to be set before the first render.
To do so, we need to create a prerender function in functions/index.js
:
jsxconst functions = require('firebase-functions'); const admin = require('firebase-admin'); const fs = require('fs'); var app = admin.initializeApp(); // Getting and replacing meta tags exports.preRender = functions.https.onRequest((request, response) => { // Error 404 is false by default let error404 = false; // Getting the path const path = request.path ? request.path.split('/') : request.path; // path[0] = erikmartinjordan.com path[1] = kpis // path[0] = erikmartinjordan.com path[1] = projects // ... // Getting index.html text let index = fs.readFileSync('./web/index.html').toString(); // Changing metas function const setMetas = (title, description) => { index = index.replace('TWITTER_DYNAMIC_TITLE', title); index = index.replace('TWITTER_DYNAMIC_DESC', description); } // Navigation menu if (path[1] === 'articles') setMetas('Articles - Erik Martín Jordán', 'Code, web development, tech and off-topic.'); else if(path[1] === 'projects') setMetas('Projects - Erik Martín Jordán', 'Websites I have deployed so far.'); else if(path[1] === 'kpis') setMetas('Kpis - Erik Martín Jordán', 'Numbers and stats.'); else if(path[1] === 'timer') setMetas('Timer - Erik Martín Jordán', 'Tasks I am working on.'); // We need to considerate the routes and a default state to 404 errors as well // ... // Sending index.html error404 ? response.status(400).send(index) : response.status(200).send(index); });
Look at the following line because it's the most important:
jsxlet index = fs.readFileSync('./web/index.html').toString();
Here, we are reading the index.html
content to replace the meta tags later on.
As we can't access the files on Firebase Hosting, we need to read the content on Firebase Functions. Therefore, we need to create a functions/web
folder and force NPM to build the index.html
inside it.
We need to modify the build script on package.json
:
terminal"scripts": { "start": "react-scripts start", "build": "react-scripts build && cp build/index.html functions/web/index.html && rm build/index.html", "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject" }
After we run npm run build
, a copy of index.html
will be stored inside functions/web
and removed from build/
.
As we are generating the HTML code using the prerender function, we don't need any index.html
in Firebase Hosting at all.
Understanding this process will lead you to one of those 'aha' moments that people talk about.
Finally, to ensure that Firebase executes the prerender function, we need to modify firebase.json
:
terminal{ "rewrites": [ { "source": "**", "function": "preRender" } ] }
And that's it. You will notice an increase in the number of function calls in Firebase; prerender function executes every time a user enters on your site.
Hi, I'm Erik, an engineer from Barcelona. If you like the post or have any comments, say hi.