I generate every blog cover with headless Chrome and a bit of CSS, no design tool
I have not opened a design tool to make a blog cover in months. Every post I publish gets a branded 1000x420 image, and each one is generated by a script that screenshots a styled HTML card with headless Chrome. The cover on this very post was made that way, in about a second, from a title and an accent color. If you publish anything on a schedule, hand-making cover or social images is exactly the kind of repetitive design work worth deleting. Here is the whole technique. The trick: headless Chrome screenshots any HTML You do not need Puppeteer or Playwright for this. The Chrome binary you already have takes a screenshot from the command line, and if you size the page precisely you get an exact-dimension PNG with no cropping: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \ --headless --disable-gpu --hide-scrollbars \ --window-size=1000,420 \ --screenshot=cover.png \ card.html --window-size=1000,420 plus a body sized to exactly 1000x420 gives you a 1000x420 image, every time. That is the entire engine. Everything else is just making card.html look good. The design lives in CSS Because the canvas is a web page, the whole look is CSS you already know. Gradients, a faint grid overlay, an accent color, a headline, a footer. Here is a trimmed version of the template: * { margin:0; box-sizing:border-box; } body { width:1000px; height:420px; overflow:hidden; font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif; } .card { width:1000px; height:420px; padding:56px 64px; color:#fff; display:flex; flex-direction:column; justify-content:space-between; background: radial-gradient(1200px 500px at 80% -10%, #38bdf822, transparent 60%), linear-gradient(135deg,#0a0f1c,#111a2e); } .kicker { letter-spacing:4px; font-weight:800; color:#38bdf8; } .title { font-size:52px; line-height:1.08; font-weight:800; letter-spacing:-1px; } .meta { font-family:ui-monospace,Menlo,monospace; color:#93a4c0; } HANDS-ON Your headline goes here whattechpost That #38bdf822 is the accent color with a low-opacity hex suffix, which is what gives the top corner its soft glow. Swap the accent per topic and every post stays on-brand while still looking distinct. Making it parametric The point is not one card, it is a card for every post from a title and a couple of arguments. So the template has placeholders, and a small Python wrapper fills them, writes a temp HTML file, and shoots it: tsize = 52 if len(title) < 46 else 44 if len(title) < 64 else 38 doc = TEMPLATE.format(kicker=html.escape(kicker), title=html.escape(title), accent=accent, tsize=tsize) with tempfile.NamedTemporaryFile("w", suffix=".html", delete=False) as f: f.write(doc); tmp = f.name subprocess.run([CHROME, "--headless", "--disable-gpu", f"--screenshot={out}", "--window-size=1000,420", "--hide-scrollbars", tmp], check=True) The one detail that earns its keep is tsize : the title font shrinks as the title gets longer, so a short punchy headline fills the card and a long one still fits on three lines instead of overflowing. It is a two-line heuristic that removes the single most annoying manual step. The gotchas I hit A few things that are not obvious until they bite: - HTML-escape the text. A title with an ampersand or an angle bracket will quietly break the layout or the whole render, so run html.escape() on every value you interpolate. - System fonts render, web fonts need help. The screenshot fires as soon as the page loads, so a @font-face pulled from the network may not arrive in time. Stick to system fonts, or preload and add a small delay. The system stack above needs nothing. - Size the body, do not crop. Exact dimensions from --window-size plus a body of the same size beats screenshotting something bigger and cropping, with no off-by-a-pixel edges. - Keep --hide-scrollbars on. Without it, content that is even slightly too tall bakes a scrollbar into the image. Why this beats a design tool here For a one-off hero image, open a real design tool. For images you make on every post, this wins on the things that compound: it is versioned in git, it diffs, it regenerates in CI, it never drifts off-brand, and it costs zero minutes per image after the template exists. I changed my accent palette once and every future cover inherited it. It also pairs with the rest of an automated publishing flow. I wrote yesterday about the walls I hit pushing posts to dev.to through its API; this is the piece that makes each of those posts show up with a cover without me touching a canvas. If you generate your own social or cover images, I would like to see your template, because the design is the fun part and I am always looking to steal a better gradient. Top comments (0)
Comments
No comments yet. Start the discussion.