So, I'm working on my first Flutter web app. In general, I got into Flutter back in 2018 because I was interested in cross-platform mobile development and embedded or IoT development. I didn't think I would develop a web-based platform in Flutter before Flutter. I was working in ReactJS, which is perfect for web-based apps; however, an opportunity from one of my clients presented itself to create a simple app using Flutter Web.
Taking on this new project, I ran into different minor challenges with Flutter Web, but one huge one was to update meta tags or open graph tags for sharing dynamically. Today, if you're creating any public platform like Reddit, Pinterest, or YouTube, when a user shares a link, you want a good preview that shows that specific user-generated content.
Here is an excellent example of me sharing a blog post on my Facebook and Slack
This is a common issue with single-page apps, but most frameworks like React, Vue, or Angular have built ways to handle this using server-side rendering, packages, or plugins. Flutter right now does not.
In this tutorial, I will cover how I was able to implement this.
I want to give a big shoutout to Tyler Savery @tylersavery for helping us with this solution and even taking the time to send us a fully working example on GitHub.
I'm using Google Firebase to host my client's app. The tutorial will cover how to add dynamic tags using Google Firebase, but of course, you can use the same methods or and backend.
We already have Firebase set up in our app as we are hosting the web app on Firebase, but if you need to set this up, you can follow the following guide.
We will need Firebase Functions to check for the bot and load a different index file. We can run the following command and setup functions in our project.
Firebase init
Now that we have Firebase Functions initiated in the project and our functions folder, we will want to copy our index.html from the Flutter build web folder and place it inside our Firebase Functions folder.
![Functions Folder]( "Functions Folder")
As you can see, I also copied my splash image, icons, and everything else I need.
Let's go ahead and install the following npm package, which we can use to detect bots.
cd functions
npm install device-detector-js
Let's add our imports and create our Firebase function.
const functions = require("firebase-functions");
const fs = require("fs");
const admin = require("firebase-admin");
const BotDetector = require("device-detector-js/dist/parsers/bot");
const DEBUG_BOT = false;
admin.initializeApp();
// Main firebase function called on firebase.json rewrites rules
exports.dynamicMetaTagsUpdate = functions.https.onRequest(async (request, response) => {
});
Now that we have a function read, we will want to load our index file, check if it's a user or bot, and, based on that, load different HTML files.
// Main firebase function called on firebase.json rewrites rules
exports.dynamicMetaTagsUpdate = functions.https.onRequest(async (request, response) => {
let html = fs.readFileSync("./index.html", "utf8");
const {id} = request.query;
const botDetector = new BotDetector();
const userAgent = request.headers["user-agent"].toString();
const bot = botDetector.parse(userAgent);
const url = "https://viz.wiijii.co/chart/?id="+ id;
// If bot get chat data from Firebase database
if (bot || DEBUG_BOT) {
try {
return response.send(html);
} catch (e) {
console.log(e);
return response.send(html);
}
}
return response.send(html);
});
Let's get some data from our Firebase database based on URL parameters.
// Main firebase function called on firebase.json rewrites rules
exports.dynamicMetaTagsUpdate = functions.https.onRequest(async (request, response) => {
// console.log("dynamicMetaTagsUpdate Called");
let html = fs.readFileSync("./index.html", "utf8");
const {id} = request.query;
const botDetector = new BotDetector();
const userAgent = request.headers["user-agent"].toString();
const bot = botDetector.parse(userAgent);
const url = "https://viz.wiijii.co/chart/?id="+ id;
// If bot get chat data from Firebase database
if (bot || DEBUG_BOT) {
try {
// console.log("running try");
const ref = admin.database().ref("charts");
const chartData = ref.child(id).get().then((snapshot) => {
if (snapshot.exists()) {
// Gets the chart data stocks for seo tags
const seoTags = [];
snapshot.child("chartData").val().forEach((element, index, array) => {
seoTags.push(element.companyName, element.stockName);
});
// Add the y and x vaules from chart data to be used in seo tags
seoTags.push(snapshot.child("xAxisValue").val(), snapshot.child("yAxisValue").val());
const object = {chartTitle: snapshot.child("chartTitle").val(), chartDescription: snapshot.child("chartDescription").val(), imageUrl: snapshot.child("imageUrl").val(), seoTags: seoTags, xAxisValue: snapshot.child("imageUrl").val()};
return object;
} else {
console.log("No data available");
}
}).catch((error) => {
console.error(error);
});
return response.send(html);
} catch (e) {
console.log(e);
return response.send(html);
}
}
return response.send(html);
});
Let's update our index with the correct open graph and SEO tags.