Internal links:
- [[Speed Pass - Nitro, the Web Framework of the unjs Family]]
- [[Speed Pass - Tencent Cloud Serverless SCF]]
- [[Write a Small Tool to Quickly Add Banners to Articles]]
- [[Speed Pass - Dynamic SVG Image Generation Solution Satori]]
Background#
Nowadays, it is common to add an image containing the current title at the top of an article. Different platforms usually consider the first image to be useful, such as considering it as the article banner, cover, hero image, etc.
Previously, in the article [[Write a Small Tool to Quickly Add Banners to Articles]], I mentioned that I created a website where you can generate banners on the page.
But is there a way to directly create a service that automatically returns an image when I visit xx.com/cover?title=xxx
? In this way, I don't need to open the page to create it. Later, I only need to check the logs to see how many people have visited!
And unlike browsers, rendering images in Node.js is a bit more complicated. You can't just throw the calculation and rendering capabilities to the browser like in a browser. The most direct way is to use HTML+CSS to generate screenshots, but in the Node environment, there is no CSS rendering capability, so you have to rely on solutions like Puppeteer and use Canvas/SVG to draw.
The cross-platform compatibility of Canvas is evident. But I'm not familiar with Canvas, so I have to learn it. Later, I found that SVG is also very simple, and it returns a string, which is even smaller in size.
You can't have your cake and eat it too.
Requirement Design#
First of all, this is a Node.js service where we send parameters to the API and get an image in return. This requires us to deploy the backend.
The same request parameters should produce consistent results, mainly for the background image. We don't want the image in the page to change when the page is refreshed, which would cause unnecessary misunderstandings.
In the future, different templates can be added, and it can be developed into a service for external use.
The parameters we hope for are:
- width: width
- height: optional, automatic height
- aspect ratio: 4:3, 16:9, 21:9, 2:1, 1:1, etc.
- title: text content
- source: watermark, such as
@ijust.cc
- specified hue: 0-360
- default gradient: linear-gradient
This is basically consistent with the web version at https://ijust.cc/tools/canvas.
Technical Design#
Delivery and Hosting#
For hosting the Node.js service, serverless is obviously a good choice. We can choose cloud services provided by domestic cloud providers or use services provided by Cloudflare/Vercel. Considering the need for migration in the future, we can choose a serverless framework. The previously mentioned [[Speed Pass - Nitro, the Web Framework of the unjs Family]] can come in handy. Nitro can easily build different products.
For cloud services, we will use Tencent Cloud Serverless Functions, and Cloudflare Workers will be added later.
Persistent Theme Color#
Consistent content should produce consistent results. Design a solution to calculate a number based on the title content to express the main color. GPT provided several solutions, such as calculating the charCodeAt for each character in the string and processing the resulting numbers.
function stringToDegrees(inputString) {
let hash = 0;
for (let i = 0; i < inputString.length; i++) {
let char = inputString.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash |= 0; // Convert the hash value to a 32-bit integer
}
return Math.abs(hash % 360);
}
stringToDegrees('中文') // 194
Using Satori to Create Dynamic SVG#
Because of the limitations of SVG technology, Satori does not support z-index and transform. It must use flex layout throughout, specify the font, does not support image formats such as webp/avif, and does not support the hsl color format.
Well, fortunately, the elements are not complex, and with some style modifications, it can be used.
I have a small avatar in my banner, and the SVG needs to be embedded using base64 to reduce the file size. To reduce the image size, I tried converting it to SVG, avif, webp, and other formats, but I was not satisfied with the results.
In the end, I used a PNG with reduced colors. I felt that 24 colors were enough, and it easily reduced the PNG size by five or six times. The difference is not visible to the naked eye, which is great.
Technical Implementation#
Here, we will prepare a new repository to explain everything, which can also be used as a sharing content in the future.
The code is available here: https://github.com/Otto-J/serverless-dynamic-banner
I'm going to write the code now and come back to add more details later.
After some time, I finally finished writing the code, and the serverless service is now online and available. A lot of complaints came up, so frustrating 😡.
Pitfall Experience#
Previously, I mentioned that the technology stack for this project is Nitro + Tencent Cloud Serverless. The goal is to deploy a serverless service and have a URL that, when opened, displays an image.
Nitro Documentation is Lacking#
I chose Nitro for two reasons:
- I don't need to learn a new syntax, such as the concepts in Next.js. It's easy to get started, and compared to Express/Koa, it's not a toy. It has a lot of built-in features like TypeScript, routing, middleware, and common methods, making it faster and easier to write real business code.
- Nitro is part of Nuxt, so learning Nitro means I can use it for backend services and full-stack services. The experience can be directly reused.
The problem is, how do I learn about the details of getting route information and setting the response? I asked GPT, but it doesn't know about this new thing. I can only rely on the documentation and tutorials.
The Nitro documentation is only a few pages long, and there is no Chinese version. The feature introduction is like a PowerPoint presentation, and there is no API index. It was frustrating, and it should be standard to have a guide, API index, playground, and project examples!
But I'm an expert, and luckily, I know that Nitro ([[Speed Pass - Nitro, the Web Framework of the unjs Family]]) is a wrapper for unjs/h3 ([[unjs_h3]]). h3 provides low-level APIs, while Nitro provides high-level abstractions that are ready to use. So I should look at the h3 documentation, smart as I am.
I searched for unjs h3 on GitHub and opened https://github.com/unjs/h3, but...
Forget it, I'll tell you. The API index is here, at the bottom of the long h3 documentation: Utilities.
How do I set the content-type to SVG when setting the response? I have to try it myself. The conclusion is to use the send
method.
By the way, if you are a Nuxt user, you need to learn the Nuxt documentation, then the Nitro documentation, and finally the h3 documentation.
Tencent Serverless Function Experience is Terrible#
As mentioned earlier ([[Speed Pass - Tencent Cloud Serverless SCF]]), as a domestic giant, Tencent Cloud should be reliable, and they have been doing this for more than a year or two.
But the experience is terrible. The process of deploying a function as I imagined:
- Authorize GitHub information and read the project.
- Select the project source code, automatically or manually select the basic information, and help me with the build.
- Get the deliverable and upload it to the cloud.
- Give me a URL, and I can access it with just one click.
- Tell me that I can customize the URL. For security and promotion of other services, it is recommended to enable gateway authentication, monitoring, logging, and other features.
- Use it. If the quota is not enough, you can purchase a package.
It sounds reasonable, serving users well and promoting other commercial services.
But in reality, it's not like that. There are errors at every step, a lot of extended technical concepts, and a lot of question mark help documents. They seem to be afraid that I will think that deploying a frontend service is simple and does not require technical expertise.
I'm too tired to complain anymore. Let's not write about it. Fortunately, I made it through.
I will create a separate document for this later.
DNSPOD Doesn't Integrate Well with Tencent Cloud#
DNSPOD was acquired by Tencent Cloud and is now part of Tencent Cloud. Even though my domain, filing, and DNS resolution are all in Tencent Cloud and DNSPOD, I still have to repeat the process of opening the page, logging in, scanning the code, and writing the configuration.
It's so troublesome.
I Stupidly Put Files in the Wrong Location#
You can see from my source code that I wrapped it in a layer to make the directory structure look nice. But I stupidly forgot and put some files outside the layer, and I couldn't access them no matter what. Oops, it's not good to have different projects in different windows, it can get messy.
Satori Doesn't Support CSS and Image Features#
Due to the limitations of SVG technology, Satori does not support z-index and transform. It must use flex layout throughout, specify the font, does not support image formats such as webp/avif, and does not support the hsl color format.
Well, fortunately, the elements are not complex, and with some style modifications, it can be used.
I have a small avatar in my banner, and the SVG needs to be embedded using base64 to reduce the file size. To reduce the image size, I tried converting it to SVG, avif, webp, and other formats, but I was not satisfied with the results.
In the end, I used a PNG with reduced colors. I felt that 24 colors were enough, and it easily reduced the PNG size by five or six times. The difference is not visible to the naked eye, which is great.
Summary and Outlook#
The project is finally completed, and I can share my experience now. I was so frustrated yesterday, but now I can say that I finished writing.
I will come back next time, hoping to be more familiar and faster.
Although there is a price war for cloud services in China, there is still room for good services. We should focus on and do well in our business. Providing a smooth user experience should be standard.