OG Images for Job Boards: Boost Apply Rates With Rich Link Previews

How to add og:title, og:image, and structured preview cards to job listing pages — so every share on Slack, LinkedIn, and Twitter drives more applicants.

Why job listing OG images matter

Job listings are shared constantly — in Discord developer communities, engineering Slack channels, Twitter replies, and LinkedIn posts. When someone shares "We're hiring a Senior Engineer" with a link, the preview card is what catches attention.

A good job listing OG image answers three questions instantly: what role,what company, and why apply. The best job boards generate unique OG images per listing automatically.

What to include in a job listing OG image

  • Company logo: Top-left, always — brand recognition builds trust
  • Job title: Large and prominent — this is the most important info
  • Location/remote status: Remote, hybrid, or specific city — huge filter for applicants
  • Compensation range: If public, showing salary increases apply rates dramatically
  • Key perks: 1-2 highlights max — equity, benefits, team size
  • Apply CTA: Optional but reinforces action

Dynamic OG image for job listings

// app/api/og/job/route.tsx
import { ImageResponse } from "next/og";

export const runtime = "edge";

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const title = searchParams.get("title") || "Open Position";
  const company = searchParams.get("company") || "";
  const location = searchParams.get("location") || "Remote";
  const salary = searchParams.get("salary") || "";
  const logo = searchParams.get("logo") || "";

  return new ImageResponse(
    (
      <div
        style={{
          display: "flex",
          flexDirection: "column",
          background: "#0f172a",
          width: "100%",
          height: "100%",
          padding: "60px",
          justifyContent: "space-between",
        }}
      >
        {/* Header: company logo + name */}
        <div style={{ display: "flex", alignItems: "center", gap: 20 }}>
          {logo && (
            <img
              src={logo}
              style={{ width: 52, height: 52, borderRadius: 10, objectFit: "cover" }}
              alt={company}
            />
          )}
          {company && (
            <div style={{ fontSize: 24, fontWeight: 700, color: "#94a3b8" }}>
              {company}
            </div>
          )}
        </div>
        
        {/* Main: job title */}
        <div style={{ display: "flex", flexDirection: "column", gap: 20 }}>
          <div
            style={{
              fontSize: title.length > 40 ? 48 : 60,
              fontWeight: 900,
              color: "#f1f5f9",
              lineHeight: 1.1,
            }}
          >
            {title}
          </div>
          
          {/* Tags row */}
          <div style={{ display: "flex", gap: 12 }}>
            <div
              style={{
                fontSize: 18,
                color: "#a78bfa",
                background: "#4c1d95",
                padding: "6px 16px",
                borderRadius: 8,
                fontWeight: 600,
              }}
            >
              📍 {location}
            </div>
            {salary && (
              <div
                style={{
                  fontSize: 18,
                  color: "#34d399",
                  background: "#064e3b",
                  padding: "6px 16px",
                  borderRadius: 8,
                  fontWeight: 600,
                }}
              >
                💰 {salary}
              </div>
            )}
          </div>
        </div>
        
        {/* Footer */}
        <div style={{ fontSize: 20, color: "#334155" }}>
          yourjobboard.com → Apply now
        </div>
      </div>
    ),
    { width: 1200, height: 630 }
  );
}

Integrating with job listing generateMetadata

// app/jobs/[id]/page.tsx
import type { Metadata } from "next";
import { getJob } from "@/lib/jobs";

export async function generateMetadata({
  params,
}: {
  params: { id: string };
}): Promise<Metadata> {
  const job = await getJob(params.id);
  
  if (!job) return { title: "Job not found" };
  
  const ogTitle = `${job.title} at ${job.company.name}`;
  const ogDescription = job.salary_range
    ? `${job.location} · ${job.salary_range} · ${job.employment_type}`
    : `${job.location} · ${job.employment_type}`;
  
  // Build dynamic OG image URL
  const ogImageParams = new URLSearchParams({
    title: job.title,
    company: job.company.name,
    location: job.location || "Remote",
    ...(job.salary_range && { salary: job.salary_range }),
    ...(job.company.logo_url && { logo: job.company.logo_url }),
  });
  
  const ogImageUrl = `/api/og/job?${ogImageParams.toString()}`;
  
  return {
    title: ogTitle,
    description: ogDescription,
    openGraph: {
      title: ogTitle,
      description: ogDescription,
      type: "website",
      images: [{ url: ogImageUrl, width: 1200, height: 630 }],
    },
    twitter: {
      card: "summary_large_image",
      title: ogTitle,
      description: ogDescription,
      images: [ogImageUrl],
    },
  };
}

JSON-LD for job listings

Add JobPosting structured data alongside your OG tags for Google Jobs integration — this can appear your listings in Google's job search results:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "JobPosting",
  "title": "Senior Frontend Engineer",
  "description": "Join our team to build...",
  "datePosted": "2026-04-01",
  "validThrough": "2026-05-01",
  "employmentType": "FULL_TIME",
  "hiringOrganization": {
    "@type": "Organization",
    "name": "Acme Corp",
    "sameAs": "https://acme.com",
    "logo": "https://acme.com/logo.png"
  },
  "jobLocation": {
    "@type": "Place",
    "address": {
      "@type": "PostalAddress",
      "addressLocality": "San Francisco",
      "addressRegion": "CA",
      "addressCountry": "US"
    }
  },
  "baseSalary": {
    "@type": "MonetaryAmount",
    "currency": "USD",
    "value": {
      "@type": "QuantitativeValue",
      "minValue": 140000,
      "maxValue": 200000,
      "unitText": "YEAR"
    }
  }
}
</script>

Common job board OG mistakes

  • Same OG image for all jobs: Using a generic company image for every listing loses the specificity that drives clicks
  • Company logo only: Without the job title in the OG image, sharers need to add context manually
  • Missing twitter:card: Required for large image preview on Twitter/X
  • Job title too long: Cap at 60 chars for OG — use a truncated version in the image
  • Dynamic image URL too long: Some platforms truncate long OG image URLs — encode parameters efficiently

Preview your job listing cards

Paste any job listing URL into OGFixer to see exactly how the preview card looks when shared on Twitter, Slack, LinkedIn, and Discord.

Check your job listing OG free →