The Problem: Unfurling Links Without JavaScript
Sharing links on platforms like Slack, LinkedIn, and X (formerly Twitter) often results in a rich preview card – a visual summary of the linked content. This unfurling process relies on bots, often called crawlers, that visit the URL to gather metadata. The challenge arises when the target application is a Single Page Application (SPA) that renders entirely in the browser and disables server-side rendering (ssr: false). These crawlers typically do not execute JavaScript. They fetch a bare HTML shell, find no og: (Open Graph) meta tags, and consequently, the link appears as a plain URL in the social feed.
Previously, the solution involved using headless browser instances like Puppeteer or Playwright. These tools automate a real browser, allowing them to render SPAs and extract dynamic content. However, this approach introduces significant overhead: increased complexity, larger deployment artifacts, and higher resource consumption. For applications hosted on serverless platforms like AWS Lambda or Cloudflare Workers, running a full browser environment is often impractical or prohibitively expensive.
The Cloud Cost Analyzer (CCA) project faced this exact issue. The goal was to share read-only scans via a link, and for these links to unfurl into informative cards displaying the brand mark, monthly savings, and number of findings. The core constraint was to avoid headless Chrome, seeking a more lightweight and efficient solution.

A Pure Rust Approach to Social Card Generation
The breakthrough came with the realization that social card generation, at its core, is about rendering HTML and extracting specific meta-information. This can be achieved without a full browser. The solution involves two key components:
1. Server-Side Rendering for Crawlers
Instead of serving the SPA directly to crawlers, a separate mechanism is needed. This can be implemented as a lightweight server or a function that specifically handles requests from known social media user agents. When a crawler requests a URL, a CloudFront function or a similar edge compute service can inspect the User-Agent string. If it matches a known crawler (e.g., Slackbot, LinkedInBot, Twitterbot), this function intercepts the request and serves a pre-rendered HTML page containing the necessary og: tags and rich content. This pre-rendered page is tailored for crawlers and doesn't require JavaScript execution.
2. Generating HTML with Rust
The core innovation is using Rust to generate this pre-rendered HTML. Rust's strengths in performance, memory safety, and its ability to compile to WebAssembly (Wasm) or native binaries make it an excellent candidate for this task. Instead of relying on Node.js and Puppeteer, developers can leverage Rust crates to:
- HTML Templating: Crates like
askamaorteraallow for generating HTML dynamically using templates, similar to server-side templating engines in other languages. This enables injecting data like savings amounts and findings counts directly into the HTML structure. - Font Rendering: A significant hurdle in generating visual content outside a browser is font rendering. Fonts are complex, and ensuring consistent rendering across different environments can be challenging. The solution here involves using a Rust library capable of parsing and rendering TrueType Font (TTF) files. One approach is to use libraries that can interpret font data and draw glyphs, which can then be embedded into the generated HTML or used to create an image. The specific challenge encountered was ensuring the correct font metrics and rendering, which often requires careful handling of font files and rendering engines.
- Image Generation (Optional but powerful): For even richer cards, Rust can be used to generate images directly. Libraries like
imagecan be used for image manipulation, and coupled with font rendering capabilities, one could generate an image of the social card. This image can then be referenced in theog:imagemeta tag.
The process flow would look like this: when a social crawler requests a link, an edge function detects it. This function then calls a Rust service (or a Wasm module) that fetches the relevant data (e.g., from an API or database). The Rust code uses its templating engine to construct the HTML, embeds necessary meta tags, and potentially renders an image. This complete HTML is then served back to the crawler.
The Font Rendering Hurdle
One of the most persistent challenges in this pure-Rust approach, as highlighted in the source, is font rendering. Unlike a browser that has a sophisticated, battle-tested font rendering engine built-in, generating text with specific fonts and styles in a server-side environment requires custom logic. The developer spent a significant afternoon wrestling with fonts. This underscores that while a headless browser handles font rendering transparently, a custom solution must explicitly manage font files, glyph metrics, and rasterization. This often involves libraries that can parse font formats (like TTF) and draw characters onto a canvas or directly into an image buffer. Ensuring text appears correctly – with proper kerning, spacing, and anti-aliasing – demands careful implementation and testing.
Benefits of the Rust Approach
Bypassing headless Chrome offers several compelling advantages:
- Reduced Complexity: Eliminates the need to manage browser binaries, WebDriver, and their dependencies, which are often substantial.
- Performance: Rust's native compilation and efficient execution can lead to faster response times for crawler requests compared to spinning up a browser instance.
- Smaller Deployments: Rust binaries or Wasm modules are significantly smaller than browser runtimes, crucial for serverless and edge computing environments.
- Cost Savings: Reduced resource usage translates directly into lower hosting costs, especially on platforms that charge per invocation or compute time.
- Cross-Platform Compatibility: Rust compiles to native code on various platforms, offering flexibility in deployment.
What This Means for Developers and Platforms
This approach democratizes sophisticated social card generation. Developers building SPAs, especially those leveraging serverless or edge computing architectures, now have a viable path to ensuring their shared links unfurl beautifully. It means richer link previews are achievable without the performance and cost penalties associated with headless browsers. For platforms that rely on crawlers to index and display link previews, this shift suggests a future where richer, more dynamic previews can be served efficiently, even from JavaScript-heavy applications. The ability to generate these cards directly within a Rust backend or edge function provides a powerful, lightweight alternative to conventional methods.
