{"id":19635514,"url":"https://github.com/henrygd/social-image-server","last_synced_at":"2025-04-28T08:30:37.098Z","repository":{"id":238002489,"uuid":"795669263","full_name":"henrygd/social-image-server","owner":"henrygd","description":"Self-hosted server to auto-generate your website's open graph social images","archived":false,"fork":false,"pushed_at":"2024-05-25T00:50:43.000Z","size":138,"stargazers_count":8,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-06-19T15:09:30.188Z","etag":null,"topics":["open-graph","opengraph","self-hosted"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/henrygd.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-05-03T19:12:56.000Z","updated_at":"2024-06-08T11:00:13.000Z","dependencies_parsed_at":"2024-05-19T02:27:09.435Z","dependency_job_id":"fe29e4ac-aa14-4ed1-b955-bc9c8a385dc8","html_url":"https://github.com/henrygd/social-image-server","commit_stats":null,"previous_names":["henrygd/social-image-server"],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/henrygd%2Fsocial-image-server","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/henrygd%2Fsocial-image-server/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/henrygd%2Fsocial-image-server/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/henrygd%2Fsocial-image-server/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/henrygd","download_url":"https://codeload.github.com/henrygd/social-image-server/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224101763,"owners_count":17256022,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["open-graph","opengraph","self-hosted"],"created_at":"2024-11-11T12:25:38.484Z","updated_at":"2024-11-11T12:25:39.456Z","avatar_url":"https://github.com/henrygd.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Social Image Server\n\nSelf-hosted server to automatically generate unique OG images for every page of your website.\n\n## Endpoints\n\n### Capture\n\nThe `/capture` endpoint generates a screen capture for any URL you pass in via the `url` query parameter.\n\n```html\n\u003cmeta property=\"og:image\" content=\"https://your-server/capture?url=turso.tech/pricing\" /\u003e\n```\n\n\u003ctable width=\"100%\"\u003e\n  \u003cthead\u003e\n    \u003ctr\u003e\n      \u003cth width=\"50%\"\u003eBefore\u003c/th\u003e\n      \u003cth width=\"50%\"\u003eAfter\u003c/th\u003e\n    \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n    \u003ctr\u003e\n      \u003ctd width=\"50%\"\u003e\u003cimg src=\"https://henrygd-assets.b-cdn.net/social-image-server/before-capture.png\" alt=\"example of turso.tech/pricing link which is missing an og:image as of may 11 2024\"/\u003e\u003c/td\u003e\n      \u003ctd width=\"50%\"\u003e\u003cimg src=\"https://henrygd-assets.b-cdn.net/social-image-server/after-capture.webp\" alt=\"example of turso.tech/pricing link using an image generated by the server as it's og:image\"/\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n### Template\n\nThe `/template` endpoint renders any static web build using variables passed in via query parameters.\n\n\u003c!-- prettier-ignore --\u003e\n```html\n\u003cmeta property=\"og:image\" content=\"https://your-server/template/example?title=Tire+Dust+Makes+Up+The+Majority+of+Ocean+Microplastics\u0026subhead=Researchers+say+tire+emissions+pose+a+threat+to+global+health%2C+and+EVs+could+make+the+problem+worse.\u0026author=Lewin+Day\u0026date=September+28%2C+2023\u0026img=https%3A%2F%2Fthedrive.com%2Fuploads%2F2023%2F09%2F28%2FGettyImages-1428297317.jpg\" /\u003e\n```\n\n\u003ctable width=\"100%\"\u003e\n  \u003cthead\u003e\n    \u003ctr\u003e\n      \u003cth width=\"50%\"\u003eBefore\u003c/th\u003e\n      \u003cth width=\"50%\"\u003eAfter\u003c/th\u003e\n    \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n    \u003ctr\u003e\n      \u003ctd width=\"50%\"\u003e\u003cimg src=\"https://henrygd-assets.b-cdn.net/social-image-server/before-template.webp?\" alt=\"example of link for article Tire Dust Makes Up The Majority of Ocean Microplastics which displays stock photo of tire\"/\u003e\u003c/td\u003e\n      \u003ctd width=\"50%\"\u003e\u003cimg src=\"https://henrygd-assets.b-cdn.net/social-image-server/after-template.png?\" alt=\"example of turso.tech/pricing link using an image generated by the server as it's og:image\"/\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n## Installation\n\n### Binary\n\nDownload and run the latest binary from the [releases page](https://github.com/henrygd/social-image-server/releases) or use the command below. You must have Chrome or Chromium installed on your system.\n\nUse the `--update` flag to update to the latest version.\n\n```bash\ncurl -sL \"https://github.com/henrygd/social-image-server/releases/latest/download/social-image-server_$(uname -s)_$(uname -m | sed 's/x86_64/amd64/' | sed 's/aarch64/arm64/').tar.gz\" | tar -xz -O social-image-server | tee ./social-image-server \u003e/dev/null \u0026\u0026 chmod +x social-image-server \u0026\u0026 ls social-image-server\n```\n\n### Docker\n\nSee the example [docker-compose](https://github.com/henrygd/social-image-server/blob/main/docker-compose.yml) or run the command below to try it out.\n\n```bash\ndocker run --rm --init -p 8080:8080 henrygd/social-image-server\n```\n\nThe image is bundled with a [slimmed down Chrome](https://github.com/chromedp/docker-headless-shell), but remains relatively lightweight -- about 10% the size of `browserless/chrome` -- and doesn't require a ton of resources.\n\n\u003e Note: The only font included in the image is DejaVu. If you want to use a different fallback font for websites that don't provide them, you need to mount your fonts in the container. See the [docker-compose](https://github.com/henrygd/social-image-server/blob/main/docker-compose.yml) for an example.\n\n## Usage\n\nAdd an `og:image` meta tag in the `\u003chead\u003e` of your website that points to your server.\n\n```html\n\u003cmeta property=\"og:image\" content=\"https://yourserver.com/capture?url=example.com/current-page\" /\u003e\n```\n\nIt's best to add this once in a layout template, rather than doing it for every page. See [Framework Examples](#framework-examples).\n\nA useful site for previewing or generating boilerplate HTML is [metatags.io](https://metatags.io/).\n\n### Templates\n\nTemplates are just static webpages -- like the output of `vite build` -- that render URL query parameters in the content.\n\nYou can use any web framework to create templates. I made the example above using Vite, Svelte, and Tailwind ([view relevant code](https://github.com/henrygd/social-image-server-template/blob/main/src/App.svelte)). The [build command](https://vitejs.dev/guide/build) generates the static files.\n\nTo add a template, create a folder containing your files in the `data/templates` directory. If your folder is called `my-template`, it would then be available at `/template/my-template`.\n\nA `url` query parameter is still required for templates. It's used to prevent abuse by verifying that the requested image matches the image used on the origin URL. If you're just testing, use `_regen_` to skip verification and a dummy string like \"test\" as the url.\n\nPlease ensure that your query parameters are encoded in your request. You can use `encodeURIComponent` in JavaScript, `url.QueryEscape` in Go, `urlencode` in PHP, `urllib.parse.quote` in Python, `URLEncoder.encode` in Java, etc.\n\n### Cache\n\nYou can refresh the cache for an image by changing any query parameter (or template name if applicable) in the origin HTML. If you're just testing, use the `_regen_` parameter.\n\nIf incoming request parameters don't match the cache, the server will verify that params on the origin URL have changed and generate a new image if so.\n\nSee [Framework Examples](#framework-examples) for examples of a version parameter that automatically refreshes the cache on new site builds.\n\n## URL Parameters\n\n| Name      | Default | Description                                                                                                                                     |\n| --------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |\n| `url`     | -       | URL to generate image for and verify against.                                                                                                   |\n| `width`   | 1400    | Width of browser viewport in pixels (max 2500). Output image is scaled to `IMG_WIDTH` width.                                                    |\n| `delay`   | 0       | Delay in milliseconds after page load before generating image.                                                                                  |\n| `dark`    | false   | Sets prefers-color-scheme to dark.                                                                                                              |\n| `format`  | -       | Image format. Defaults to `IMG_FORMAT` value if not specified.                                                                                  |\n| `_regen_` | -       | Do not use in public URLs. Testing only. Skips origin verification and forces full regeneration on every request. Must match `REGEN_KEY` value. |\n\n## Environment Variables\n\n| Name              | Default | Description                                                                                                                        |\n| ----------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------- |\n| `ALLOWED_DOMAINS` | -       | Restrict to certain domains. Example: \"example.com,example.org\"                                                                    |\n| `CACHE_TIME`      | 30 days | Time to cache images on server. Minimum 1 hour.                                                                                    |\n| `DATA_DIR`        | ./data  | Directory to store program data (images and database).                                                                             |\n| `FONT_FAMILY`     | -       | Change browser fallback font. Must be available on your system / image.                                                            |\n| `IMG_FORMAT`      | jpeg    | Default format if not specified in request. Valid values: \"jpeg\", \"png\".                                                           |\n| `IMG_QUALITY`     | 92      | Compression quality (jpeg only).                                                                                                   |\n| `IMG_WIDTH`       | 2000    | Width of output image in pixels.                                                                                                   |\n| `LOG_LEVEL`       | info    | Logging level. Valid values: \"debug\", \"info\", \"warn\", \"error\".                                                                     |\n| `MAX_TABS`        | 5       | Maximum number of active browser tabs. 2 or 3 is fine in most cases.                                                               |\n| `PERSIST_BROWSER` | 5m      | Time to keep the browser process running after the last image generation. Valid units: \"ms\", \"s\", \"m\", \"h\". See FAQ for more info. |\n| `PORT`            | 8080    | Port to listen on.                                                                                                                 |\n| `REGEN_KEY`       | -       | Key used to force bypass cache.                                                                                                    |\n| `REMOTE_URL`      | -       | Connect to an existing Chrome or Chromium instance using WebSocket. Example: wss://localhost:9222                                  |\n\n## Frequently Asked Questions\n\n### Does this require Chrome / Chromium running in the background indefinitely?\n\nOnly if you use `REMOTE_URL` to connect with a remote browser process. Otherwise the server will manage headless browser processes as necessary.\n\nIf it needs to generate an image and there is no process already running, it will start one. After the image is generated, a timer is started. If another image generation occurs within the timer duration, the browser process is reused, and the timer is reset. If the timer reaches zero, the browser process is terminated.\n\nThis way active servers get the performance benefits of reusing the browser process, while less active servers will not need to keep Chrome running in the background forever.\n\nThe timer duration can be configured with the 10`PERSIST_BROWSER` environment variable.\n\n### How can I add custom styles or scripts when the screenshot is taken?\n\nThe server's outgoing request to websites always includes the URL parameter `og-image-request=true`, so check for that. Add a short delay if you're doing the check on the front end.\n\n### Why does the image look different than in my browser?\n\nProbably because the website isn't providing fonts over the network, and the browser has a different default font than your personal setup.\n\nYou should be able to change the font using the `FONT_FAMILY` environment variable. If using docker, mount your fonts in the container. Check the [docker-compose](https://github.com/henrygd/social-image-server/blob/main/docker-compose.yml) for a commented-out example.\n\nIf you're using a remote browser (not recommended), try setting the `--system-font-family` flag on the process.\n\n### Why is webp not an image format option?\n\nFrom what I can tell, Facebook and LinkedIn (and likely others) don't support webp open graph images. If I'm wrong, let me know and I'll add it.\n\n### I changed my image but Twitter is still showing the old one\n\nTwitter seems to cache images for a long time. Try adding or removing a trailing slash, query parameter, or fragment to the URL in your tweet.\n\n## Security recommendations\n\n- **Do not run a public server without setting `ALLOWED_DOMAINS`**. Without restrictions, an attacker can use your browser to visit a malicious URL.\n- **Do not leak your regen key in your HTML**. The regen key force bypasses the cache and URL status verification, so an attacker can attempt to DoS the server by sending thousands of requests to different URL paths. If you think you may have leaked it, change the `REGEN_KEY` environment variable or remove it entirely.\n- **Keep `MAX_TABS` to a reasonable value**. Your OG images are cached both on the server and usually by the service you're sharing to, so it's unlikely that you'll be handling lots of simultaneous image generations. Most servers will be fine with 2 or 3 max tabs. If all tabs are in use, new requests are just queued until one of the tabs is free.\n\n## Remote Browser\n\nUse the `REMOTE_URL` environment variable to connect to a remote instance of Chrome or Chromium over WebSocket.\n\n\u003e [!IMPORTANT]\n\u003e This approach is only recommended if you already have an existing browser process running full time. The server cannot stop / start the process, so it will need to run independently for the lifetime of the server.\n\u003e\n\u003e I'd also recommend using the binary, since it doesn't come bundled with its own browser, though I can make a tiny docker image with only the server if there's any demand for it.\n\nIf you're using a container image for Chrome, check the documentation for a port (usually 9222) or address to connect to. If you're using the server binary, expose the Chrome container's port to the host: `127.0.0.1:9222:9222`. If using the docker version, put the container in the same network as Chrome and connect using its container name: `wss://chrome-container:9222`.\n\nIf using Chrome directly, set the `--remote-debugging-port` flag. Note that if you're running the server as a container you will need to give it access to your host ports.\n\n```sh\ngoogle-chrome-stable --remote-debugging-port=9222 --headless=new --hide-scrollbars --font-render-hinting=none --disable-background-networking --enable-features=NetworkService,NetworkServiceInProcess --disable-extensions --disable-breakpad --disable-backgrounding-occluded-windows --disable-default-apps --disable-background-timer-throttling --disable-features=site-per-process,Translate,BlinkGenPropertyTrees --disable-hang-monitor --disable-client-side-phishing-detection --disable-popup-blocking --disable-prompt-on-repost --disable-sync --disable-translate --metrics-recording-only --no-first-run --password-store=basic --use-mock-keychain\n```\n\n## Response headers\n\nThe server includes status headers for successful image requests.\n\n### X-Og-Cache\n\n| Value | Description             |\n| ----- | ----------------------- |\n| HIT   | Cached image was served |\n| MISS  | New image was generated |\n\n### X-Og-Code\n\n| Value | Description                                                                   |\n| ----- | ----------------------------------------------------------------------------- |\n| 0     | New image generated because it did not exist in cache                         |\n| 1     | New image generated due to `_regen_` parameter                                |\n| 2     | Found matching cached image                                                   |\n| 3     | Request does not match og:image on origin URL. Using previously cached image. |\n\n## Framework examples\n\nThese examples use a query parameter `v` to bypass cache on new builds, but you can remove it if you don't need that functionality. Feel free to improve these or contribute others.\n\n### SvelteKit\n\nMake sure you set your website's origin in [prerender settings](https://kit.svelte.dev/docs/configuration#prerender).\n\n```svelte\n\u003c!-- +layout.svelte --\u003e\n\u003cscript\u003e\n  import { version } from '$app/environment'\n  import { page } from '$app/stores'\n\u003c/script\u003e\n\n\u003csveltekit:head\u003e\n  \u003cmeta\n    property=\"og:image\"\n    content=\"https://your-server/capture?url={$page.url.toString()}\u0026v={version}\"\n  /\u003e\n\u003c/sveltekit:head\u003e\n```\n\n### Astro\n\n```astro\n\u003c!-- Layout.astro --\u003e\n---\nimport {version} from '../../package.json';\n---\n\n\u003chead\u003e\n  \u003cmeta\n    property=\"og:image\"\n    content={`https://your-server/capture?url=${Astro.url}\u0026v=${version}`}\n  /\u003e\n\u003c/head\u003e\n```\n\n### WordPress\n\n```php\n// header.php\n\u003c?php\n  $page_url = home_url($_SERVER['REQUEST_URI']);\n  $version = wp_get_theme()-\u003eget('Version');\n  $og_image_url = \"https://your-server/capture?url=$page_url\u0026v=$version\";\n?\u003e\n\n\u003chead\u003e\n  \u003cmeta property=\"og:image\" content=\"\u003c?php echo $og_image_url ?\u003e\"/\u003e\n\u003c/head\u003e\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhenrygd%2Fsocial-image-server","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhenrygd%2Fsocial-image-server","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhenrygd%2Fsocial-image-server/lists"}