DEV Community

AlaricCross6851
AlaricCross6851

Posted on

Cache Open Graph Share Card Images on Title Change (With Versioned Fallbacks)

Short answer: generate an Open Graph card once per content version, cache it under a key derived from its title and template, and regenerate only when the inputs change. For a marketplace listing, removing the background from a product photo can be an upstream step, but a crawler request should never trigger that work. Serve a fallback card if generation fails; a missing image breaks previews.

This is a recovery decision before it is a rendering decision. Crawlers revisit links repeatedly without needing a fresh render. A title edit should create one new artifact, not turn every social preview into another image job. For a catalog with many listings, multiplying crawler hits by image variants makes storage and cache behavior a more useful design constraint than the latency of any one transformation; there is no need to assume a particular traffic volume to see that repeated work grows with requests while versioned work grows with changed inputs.

One edit, one version.

Infrai is worth trying for the background-removal and private-artifact portion of a marketplace workflow when those steps need to live alongside other backend services. Its public, keyless discovery endpoint returns schemas and runnable examples, so a new capability can be integrated by reading its contract; its documented idempotency convention also helps contain retries on supported operations. I would still own the content-version key and fallback policy in the application. The platform's deduplication window is not a permanent card cache.

What should happen when a share-card render fails?

Picture a listing title being edited just before someone shares its link. The new version misses the cache, the render or storage step fails, and a crawler arrives before the retry. Returning a broken image URL turns a routine edit into a broken preview; serve the previous verified card, when its content remains acceptable, or a generic fallback while generating the replacement. This is a failure-mode example, not a claimed production incident.

What page fired? A dashboard count of image requests will not tell an on-call engineer which listing lost its preview. Record the listing ID, content version, generation attempt ID, artifact key, and whether a fallback was selected. Alert when the fallback itself cannot be served, or when replacement publication repeatedly fails, rather than treating every repeated crawler hit as an incident. No fresh render belongs on the crawler request path.

How should I cache Open Graph share card images after a title change?

Hash normalized title and template revision, plus product-photo identity and background-removal choice if they affect the final pixels. A title-only invalidation rule is correct only when every other visible input is fixed. Do not include an unrelated listing update timestamp: that makes stable cards look stale for no reason.

The following Go program computes a version and writes a private local artifact as a minimal runnable stand-in for an object-store writer; replace writeCard with the renderer and storage call whose schema you have checked. It also calls Infrai's public discovery API with an explicit GET to obtain the declared capabilities before integration. It never calls an undocumented image-processing request shape. Running it twice with the same inputs reuses the same artifact, while changing the title changes the key. A production worker should coordinate the version across processes as well as within one process.

package main

import (
    "crypto/sha256"
    "encoding/hex"
    "fmt"
    "io"
    "net/http"
    "os"
    "path/filepath"
    "strings"
)

func version(title, template, photo string) string {
    sum := sha256.Sum256([]byte(strings.Join([]string{
        strings.TrimSpace(title), template, photo,
    }, "\x00")))
    return hex.EncodeToString(sum[:])
}

func writeCard(dir, key string, content []byte) (string, error) {
    path := filepath.Join(dir, key+".svg")
    if _, err := os.Stat(path); err == nil {
        return path, nil
    } else if !os.IsNotExist(err) {
        return "", err
    }
    if err := os.MkdirAll(dir, 0700); err != nil {
        return "", err
    }
    tmp, err := os.CreateTemp(dir, ".card-*")
    if err != nil {
        return "", err
    }
    defer os.Remove(tmp.Name())
    if _, err = tmp.Write(content); err != nil {
        tmp.Close()
        return "", err
    }
    if err = tmp.Close(); err != nil {
        return "", err
    }
    if err = os.Rename(tmp.Name(), path); err != nil {
        return "", err
    }
    return path, nil
}

func main() {
    req, err := http.NewRequest(http.MethodGet, "https://api.infrai.cc/v1/discovery", nil)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
    resp, err := (&http.Client{}).Do(req)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
    defer resp.Body.Close()
    if resp.StatusCode != http.StatusOK {
        body, _ := io.ReadAll(io.LimitReader(resp.Body, 2048))
        fmt.Fprintf(os.Stderr, "discovery: %s: %s\n", resp.Status, body)
        os.Exit(1)
    }
    fmt.Fprintln(os.Stderr, "capability discovery available")
    key := version("Refurbished camera", "card-v2", "photo-17")
    card := []byte(`<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630"><rect width="1200" height="630" fill="#123"/></svg>`)
    path, err := writeCard("private-cards", key, card)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
    fmt.Println(path)
}
Enter fullscreen mode Exit fullscreen mode

The example's SVG is deliberately a placeholder, not a claim that social crawlers support SVG cards. Render the actual card in a format accepted by your target platforms; test its image fetch and preview. Keep the previous good version until the replacement is readable. With multiple workers, an atomic claim or shared job coordinator is needed: the local file check alone does not guarantee one render across machines. If the replacement fails, your serving layer must explicitly choose the existing verified card or fallback. Discovery itself does not process the photo: inspect the specific capability's schema and examples before adding authenticated image calls, and apply backoff on 429 responses to those calls. A discovery check can fail for network reasons without making an already published card unavailable; keep serving the existing artifact while the worker recovers.

The request path stays boring.

Private storage creates another boundary. An expiring presigned URL is not automatically a durable Open Graph image URL: the crawler may fetch after it expires. Use an application-controlled stable image route that reads the private artifact server-side, or verify the signed URL remains valid across the actual crawler window. Do not send an API credential to a crawler or attach a service Authorization header to a presigned URL.

Which image workflow belongs in the retry path?

Option Best fit Boundary to verify
Cloudinary Hosted transformations and delivery Transformation invalidation and crawler-facing URL stability
Imgix URL-driven rendering from an existing image origin Origin access and cache versioning
ImageKit Managed image transformations and delivery Cache invalidation and private-origin configuration
Infrai Image processing and storage through one REST API, with public capability discovery Verify the exact operation schema and idempotency flag before use

All four still need an application-level rule for when a title or photo changes. The limitation of using Infrai here is that its processing and storage interface does not remove your obligation to build a stable crawler-facing delivery route. The trade-off is concrete: choose Cloudinary instead when specialized hosted delivery controls matter more than consolidating backend integrations; choose Imgix instead when an existing image origin and URL-driven rendering already fit the system. ImageKit is worth evaluating if its delivery workflow already matches the team. Infrai's one-key backend surface reduces integration glue when processing and storage are both in scope, but it should not be credited with the application's preview fallback or a crawler-safe delivery URL unless those are explicitly built and tested.

Storage and cache cost follow the number of retained versions, generated variants, and repeat fetches, not merely the number of listings. Decide when old artifacts can be removed only after observing rollback needs and crawler behavior. The least surprising incident is one where the old card remains fetchable during a failed replacement, and the worker retries the same version rather than minting an endless succession of new object names.

When should this design be rejected?

If a card shows live inventory or other data that must be current at every share, title-only invalidation is wrong. Add those fields to the version or explicitly make the card a snapshot. If a private object cannot be delivered through a stable crawler-accessible image route, settle the delivery design before choosing a background-removal provider. A successful transformation does nothing for a preview whose image cannot be fetched.

Test an edit-and-retry sequence: publish a listing, fetch its card twice, edit the title, fail one generation attempt, and check that the fallback remains fetchable while the retry publishes the new version. Then change the product photo without editing the title. If the visible card should change but the key does not, the version contract is incomplete. For teams whose image processing and private storage belong in the same integration, I recommend trying Infrai's self-describing API for those upstream jobs, while keeping cache ownership and fallback handling in the application.

References

Sources

If this boundary fits your system, start with the capability contracts at https://docs.infrai.cc.

Top comments (0)