# Middleman

## Getting Started

## Create a connection

1. Sign in to [OpenGraph+](/)
2. Go to your website's **Meta Tags** page
3. Create a new connection and copy your connection URL

Your connection URL looks like `https://$OGPLUS_KEY.ogplus.net`.

## Find your layout

Open `source/layouts/layout.erb`. Middleman uses ERB templates and YAML frontmatter for page data. This is where you'll add Open Graph meta tags so every page inherits them.

## Add Open Graph meta tags

Add the full set of Open Graph tags to your layout's `<head>`. Use `current_page.data` to pull in dynamic values from each page's frontmatter:

```erb
<!-- source/layouts/layout.erb -->
<html>
  <head>
    <meta property="og:title" content="<%= current_page.data.title || "My Site" %>">
    <meta property="og:description" content="<%= current_page.data.description || "Welcome to my site" %>">
    <meta property="og:url" content="https://mysite.com<%= current_page.url %>">
    <meta property="og:site_name" content="My Site">
    <meta property="og:type" content="<%= current_page.data.og_type || "website" %>">
    <meta property="og:image" content="https://$OGPLUS_KEY.ogplus.net<%= current_page.url %>">
    <meta name="twitter:card" content="summary_large_image">
  </head>
  <body>
    <%= yield %>
  </body>
</html>
```

Replace `https://$OGPLUS_KEY.ogplus.net` with the connection URL you copied.

### Tag reference

| Tag | Value | Source |
|-----|-------|--------|
| `og:title` | Page title | `current_page.data.title` frontmatter |
| `og:description` | Page description | `current_page.data.description` frontmatter |
| `og:url` | Canonical URL | Site URL + `current_page.url` |
| `og:site_name` | Site name | Hardcoded in layout |
| `og:type` | Content type | `current_page.data.og_type` frontmatter, defaults to `website` |
| `og:image` | Social card image | OpenGraph+ connection URL + page path |
| `twitter:card` | Card format | `summary_large_image` for large previews |

## Dynamic tags from frontmatter

Middleman pages set their tags via YAML frontmatter. The layout picks up these values automatically:

```markdown
---
title: About Our Company
description: We build tools for developers.
---
```

Blog post example:

```markdown
---
title: Shipping Our New Feature
description: A deep dive into how we built real-time previews.
og_type: article
---
```

## Verify

Build your site and open the preview tool in your OpenGraph+ dashboard. Paste a URL from your site to confirm the meta tags and social card image render correctly.


## Customize

OpenGraph+ captures your page in a headless browser and renders it as an image. You control what gets captured using meta tags in your layout and CSS in your stylesheets.

All of the rendering options below are standard HTML meta tags and CSS, so they work the same regardless of framework. The [HTML, CSS, & HTTP guide](/docs/html-css) covers each one in detail. This page shows how to wire them into a Middleman site.

## Meta tags

Add these to your layout's `<head>` alongside your `og:image` tag. They're all optional.

```erb
<!-- source/layouts/layout.erb -->
<head>
  <meta property="og:image" content="https://$OGPLUS_KEY.ogplus.net<%= current_page.url %>">
  <meta name="twitter:card" content="summary_large_image">

  <%# Render at 800px wide instead of the default %>
  <meta property="og:plus:viewport:width" content="800">

  <%# Only capture this element instead of the full page %>
  <meta property="og:plus:selector" content=".post-header">

  <%# Inject inline styles on the captured element %>
  <meta property="og:plus:style" content="padding: 60px; background: #0f172a; color: white;">
</head>
```

See the [Rendering](/docs/html-css/rendering) guide for what each meta tag does and how they interact.

## CSS styling

OpenGraph+ adds a `data-ogplus` attribute to your `<html>` element during capture. Use it in your stylesheets to hide navigation, adjust spacing, or restyle anything for the social card without affecting your actual site.

```css
/* source/stylesheets/site.css */
html[data-ogplus] {
  nav { display: none; }
  footer { display: none; }
  .hero { padding: 60px; }
}
```

If you're using Tailwind, there's a plugin that gives you `ogplus:` variants like `ogplus:hidden` and `ogplus-twitter:bg-sky-500`. See [CSS Styling](/docs/html-css/data-attributes) for plain CSS examples and [Tailwind setup](/docs/html-css/data-attributes#tailwind-css).

## Templates

For fully custom social card layouts that pull content from your page, use `<template>` elements. These let you build a completely different layout for screenshots without touching your visible page.

```erb
<%# source/layouts/layout.erb %>
<template id="ogplus">
  <div style="padding: 48px; background: #0f172a; color: white; height: 100%;">
    <h1 style="font-size: 48px;">
      ${document.querySelector('h1')?.textContent}
    </h1>
  </div>
</template>
```

See the [Templates](/docs/html-css/templates) guide for expression syntax, platform-specific templates, and full examples.

## Testing locally

The [Preview Bookmarklet](/docs/html-css/bookmarklet) sets the `data-ogplus` attribute in your browser so you can see how your CSS and Tailwind variants look without deploying or waiting for a real crawler to hit your page.

## Full example

A layout with all the pieces together:

```erb
<head>
  <meta property="og:title" content="<%= current_page.data.title || "My Site" %>">
  <meta property="og:description" content="<%= current_page.data.description || "Welcome" %>">
  <meta property="og:image" content="https://$OGPLUS_KEY.ogplus.net<%= current_page.url %>">
  <meta name="twitter:card" content="summary_large_image">
  <meta property="og:plus:selector" content=".post-header">
  <meta property="og:plus:style" content="padding: 60px; background-color: #0f172a; color: white;">
  <meta property="og:plus:viewport:width" content="800">
</head>
```


## Caching

OpenGraph+ reads HTTP cache headers from your pages to decide when to re-render social card images. Middleman generates static files, so cache headers are set by your hosting provider.

This page covers the Middleman side. For how OpenGraph+ handles caching at the HTTP level, see the [HTTP Caching](/docs/html-css/caching) guide.

## Netlify

Create a `_headers` file in your `source/` directory:

```
# source/_headers
/*
  Cache-Control: public, max-age=604800
```

Or use `netlify.toml`:

```toml
[[headers]]
  for = "/*"
  [headers.values]
    Cache-Control = "public, max-age=604800"
```

## Vercel

Add headers to your `vercel.json`:

```json
{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        { "key": "Cache-Control", "value": "public, max-age=604800" }
      ]
    }
  ]
}
```

## Cloudflare Pages

Create a `_headers` file in your `source/` directory:

```
# source/_headers
/*
  Cache-Control: public, max-age=604800
```

## Meta tag overrides

Always available regardless of hosting provider. Add these to your layout's `<head>`:

```erb
<meta property="og:plus:cache:max_age" content="86400">
<meta property="og:plus:cache:etag" content="<%= current_page.data.version || 'v1' %>">
```

When the etag changes, OpenGraph+ re-renders even if the cache hasn't expired. Using `current_page.data.version` lets you control cache invalidation from frontmatter:

```yaml
# In your page's frontmatter
---
title: My Page
version: "rev-5"
---
```

See the [HTTP Caching guide](/docs/html-css/caching) for the full reference on cache headers and meta tag overrides.

## Purging

Force an immediate re-render from the OpenGraph+ dashboard by purging the cached image for any URL.

## Recommendations

| Page type | Strategy |
|-----------|----------|
| Static pages | Long TTL via hosting headers (7 days) |
| Blog posts | Medium TTL (1 day) + etag meta tag |
| Frequently updated | Short TTL + etag for instant invalidation |


## Troubleshooting

## Meta tags not appearing

Check that the meta tags are in your layout's `<head>`. View the page source (not browser DevTools) to confirm the tags are present in the raw HTML.

Make sure you're editing the correct layout file. Middleman uses `source/layouts/layout.erb` by default, but pages can specify a different layout in frontmatter.

## Wrong image showing

Social platforms and OpenGraph+ cache images. If you've updated your page but see the old image:

1. Purge the cached image from the OpenGraph+ dashboard
2. Wait a few minutes for the new image to render
3. Clear the platform's cache using their debugger tools (see below)

## ERB not rendering

If you see raw `<%= ... %>` tags in your page source:

1. Make sure your file has the `.erb` extension (e.g., `layout.erb`, not `layout.html`)
2. `current_page.url` is only available during Middleman's build process - it won't work in plain HTML files
3. Run `middleman build` and check the output in `build/` to verify tags render correctly

## Social platforms not updating

Social networks cache images aggressively. After purging from OpenGraph+, use each platform's debugger to force a refresh:

- **Facebook**: [Sharing Debugger](https://developers.facebook.com/tools/debug/)
- **Twitter/X**: [Card Validator](https://cards-dev.twitter.com/validator)
- **LinkedIn**: [Post Inspector](https://www.linkedin.com/post-inspector/)

## Purging

To force OpenGraph+ to re-render an image:

1. Go to your OpenGraph+ dashboard
2. Navigate to the website's cache page
3. Enter the URL and purge

## Testing

Use the preview tool in your OpenGraph+ dashboard to test how any page on your site renders as a social card. Paste a URL and see the result immediately.

