辛宝Otto

辛宝Otto 的玄酒清谈

北漂前端程序员儿 / 探索新事物 / Web Worker 主播之一/内向话痨
xiaoyuzhou
email

Using Nitro + Serverless functions to implement dynamic header image banners.

image.png

Internal link associations:

  • [[Quick access to the web framework Nitro in the unjs family]]
  • [[Quick access to Tencent Cloud Cloud Function scf]]
  • [[Write a small tool to quickly add banners to articles]]
  • [[Quick access to the 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 often use the first image as the article banner, cover, hero image, etc.

In a previous article, [[Write a small tool to quickly add banners to articles]], I mentioned that I created a website where I can generate banners on the page.

Is there a way to directly create a service that automatically returns an image when I visit xx.com/cover?title=xxx? This way, I don't need to open the page to create it. I can simply check the logs to see how many people have visited!

Unlike browsers, rendering images in Node.js is a bit more complicated. We can't simply delegate the computation and rendering capabilities to the browser. The most direct approach is to use HTML+CSS to generate screenshots, but this is not possible in the Node.js environment because there is no CSS rendering capability. We have to rely on solutions like Puppeteer and use canvas/SVG to draw.

The cross-platform compatibility of Canvas becomes apparent in this case. However, I am not familiar with Canvas, so I need to learn it. Later, I discovered that SVG is also very simple and the returned result is a string, which is smaller in size.

You can't have your cake and eat it too.

Requirements Design

First of all, this is a Node.js service where we make a request with parameters and receive an image in return. This requires us to deploy the backend.

The same request parameters should produce consistent results, especially for the background image. We don't want the image to change every time the page is refreshed, causing unnecessary confusion.

In the future, we can add different templates and make it a service for external use.

The parameters we hope to have are:

  • width: the width of the image
  • height: optional, automatically determined height
  • aspect ratio: 4:3, 16:9, 21:9, 2:1, 1:1, etc.
  • title: the text content
  • source: the 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, we can use serverless. We can choose services provided by domestic cloud providers or use services provided by Cloudflare/Vercel. Considering the need for future migration, we can choose the serverless framework, which I mentioned before, [[Quick access to the web framework Nitro in the unjs family]]. Nitro can easily build different products.

For cloud providers, we will use Tencent Cloud Serverless Functions, and we will also supplement with Cloudflare Workers later.

Persistent Theme Color

To ensure consistent results for the same content, we need to design a solution to calculate a number based on the title content to represent the main color. I asked GPT and it provided several solutions, such as calculating the charCodeAt value for each character in the string.

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

Due to the considerations of SVG technology, Satori does not support z-index, transform, and must use flex layout throughout. The font must be specified, and it does not support image formats such as webp/avif. It also does not support the hsl color format.

To embed the SVG image of the avatar in my banner, I tried converting it to base64 and embedding it. However, this increased the file size. In order to reduce the image size, I tried converting it to SVG, avif, webp, etc., but I was not satisfied with the results.

In the end, I used a PNG with reduced colors. I found that 24 colors were sufficient, and it reduced the PNG size by five to six times without any noticeable difference. It worked well.

Technical Implementation

I will prepare a new repository to explain everything and it 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 will go write the code now and come back to update later.

After some time, I finally finished writing the code and the serverless service is now online and available. I encountered a lot of frustrations, it's infuriating 😡.

Pitfall Experience

As mentioned before, the technology stack for this project is Nitro + Tencent Cloud Serverless. The goal is to deploy a serverless service and have a URL that opens an image.

Nitro Documentation is Terrible

I chose Nitro for two reasons:

  • I don't need to learn a new syntax, such as the concepts in Next.js. It is easy to get started and compared to Express/Koa, it is 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 once I learn Nitro, 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 technical details such as obtaining 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, there is no Chinese version, and the feature introduction is like a PowerPoint presentation. There is no playground, and it was difficult to read. I couldn't find the API index either. I was angry. A guide + API index + playground + project practice should be standard!

image.png

But I am an expert, luckily I know that Nitro ([[Quick access to the web framework Nitro in 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. The result...

image.png

Forget it, I'll tell you. The API index is here, at the bottom of the long h3 documentation: Utilities.

image.png

How do I set the content-type to SVG when setting the response? I had 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.

Terrible Experience with Tencent Serverless Functions

As mentioned in the documentation ([[Quick access to Tencent Cloud Cloud Function scf]]), Tencent is a trusted domestic cloud provider and they have been doing this for more than a year or two.

But the experience was terrible. This is what I expected for the process of deploying a cloud function:

  • Authorize GitHub information and read the project
  • Select the project source code, automatically or manually select basic information, and help me with the build
  • Get the deliverable and upload it to the cloud
  • Give me a URL, and when I click on it, it works
  • Tell me that I can customize the URL. For security and promotion of other services, it is recommended to enable gateway authentication, monitoring, logging, etc.
  • Use it, if the quota is not enough, you can buy a package

It sounds reasonable, providing good service to users and promoting other commercial services.

But in reality, every step had a lot of errors, a lot of extended technical concepts, and a lot of question mark help documentation. It seemed like they were afraid that I would think deploying a frontend service was simple and had no technical difficulty.

I can't even complain anymore, forget it, I won't write about it. Luckily, I made it through.

image.png

image.png

image.png

I will create a separate document for this later.

DNSPOD Doesn't Integrate Well with Tencent Cloud

DNSPOD was acquired by Tencent Cloud and now belongs to Tencent Cloud. Even though my domain, record, and resolution are all in Tencent Cloud and DNSPOD, I still have to repeat the process of opening a page, logging in, scanning a QR code, and writing the configuration.

It's very troublesome.

I Stupidly Put Files in the Wrong Location

You can see from my source code that for the sake of having a nice directory structure, I wrapped it in another layer. But I forgot about it and put some files outside. No matter what I did, I couldn't access them. Oops, it's not good to have different projects open 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, transform, and must use flex layout throughout. The font must be specified, and it does not support image formats such as webp/avif. It also does not support the hsl color format.

Well, fortunately, the things I needed were not complicated, and I was able to make it work by making some changes to the styles.

I have a small avatar in my banner, and I need to embed it in the SVG using base64, which increases the file size. In order to reduce the image size, I tried converting it to SVG, avif, webp, etc., but I was not satisfied with the results.

In the end, I used a PNG with reduced colors. I found that 24 colors were sufficient, and it reduced the PNG size by five to six times without any noticeable difference. It worked well.

Summary and Outlook

I have finished the project, and I can finally share my experience after being frustrated yesterday.

Next time, I hope to be more familiar with the process and be faster.

Although there is a price war for cloud services in China, there is still room for improvement in terms of service quality. Smooth service usage should be a standard.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.