Alpine.js Open Graph Meta Tags: Static HTML OG for Alpine Apps

Alpine.js renders entirely in the browser — here's how to add og:title, og:image, og:description, and Twitter card meta tags in the static HTML served to crawlers.

Why Alpine.js needs static OG tags

Alpine.js is a lightweight JavaScript framework designed to sprinkle interactivity onto server-rendered HTML. Unlike React or Vue SPAs, Alpine.js doesn't control routing or rendering — your server (PHP, Node, Rails, etc.) sends complete HTML to the browser, and Alpine.js enhances it on the client.

Social crawlers like Twitterbot and LinkedInBot don't execute JavaScript. They read the raw HTML your server sends. This means your og:title, og:image, and og:description tags must be in the server-rendered HTML — not added by Alpine.js after the page loads.

The good news: because Alpine.js apps are already server-rendered, adding OG tags is straightforward. You just need to put them in the <head> of your HTML template.

Add OG tags to static HTML

For static sites or simple Alpine.js pages, add OG meta tags directly in the <head> of each HTML file. Alpine.js lives entirely in the <body> — your <head> is just regular HTML.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>My Alpine.js App — Fast Interactivity</title>
    <meta name="description" content="Lightweight interactivity with Alpine.js and server-rendered HTML." />

    <!-- Open Graph -->
    <meta property="og:type"        content="website" />
    <meta property="og:title"       content="My Alpine.js App — Fast Interactivity" />
    <meta property="og:description" content="Lightweight interactivity with Alpine.js." />
    <meta property="og:image"       content="https://yourdomain.com/og/home.png" />
    <meta property="og:url"         content="https://yourdomain.com/" />
    <meta property="og:site_name"   content="My App" />

    <!-- Twitter Card -->
    <meta name="twitter:card"        content="summary_large_image" />
    <meta name="twitter:title"       content="My Alpine.js App — Fast Interactivity" />
    <meta name="twitter:description" content="Lightweight interactivity with Alpine.js." />
    <meta name="twitter:image"       content="https://yourdomain.com/og/home.png" />

    <!-- Alpine.js -->
    <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
  </head>
  <body>
    <!-- Alpine.js components go here -->
    <div x-data="{ open: false }">
      <!-- ... -->
    </div>
  </body>
</html>

Server-side OG with Alpine + PHP backend

If your Alpine.js app uses a PHP backend (a common pattern with Laravel Blade, WordPress, or plain PHP), inject dynamic OG tags from your server-side template. The meta tags are rendered server-side; Alpine.js handles interactivity after the page loads.

<?php
// In your PHP controller or template
$post = getPostBySlug($slug);
?>
<!DOCTYPE html>
<html lang="en">
  <head>
    <title><?= htmlspecialchars($post['title']) ?> | My Blog</title>
    <meta name="description" content="<?= htmlspecialchars($post['excerpt']) ?>" />

    <!-- Open Graph -->
    <meta property="og:type"        content="article" />
    <meta property="og:title"       content="<?= htmlspecialchars($post['title']) ?>" />
    <meta property="og:description" content="<?= htmlspecialchars($post['excerpt']) ?>" />
    <meta property="og:image"       content="<?= htmlspecialchars($post['og_image']) ?>" />
    <meta property="og:url"         content="https://yourdomain.com/blog/<?= $post['slug'] ?>" />

    <!-- Twitter Card -->
    <meta name="twitter:card"        content="summary_large_image" />
    <meta name="twitter:title"       content="<?= htmlspecialchars($post['title']) ?>" />
    <meta name="twitter:description" content="<?= htmlspecialchars($post['excerpt']) ?>" />
    <meta name="twitter:image"       content="<?= htmlspecialchars($post['og_image']) ?>" />

    <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
  </head>
  <body x-data>
    <!-- Alpine.js components -->
  </body>
</html>

Per-page OG with a Node.js backend

If your Alpine.js app is backed by Express or another Node.js server, render your HTML templates with a view engine like EJS, Handlebars, or Nunjucks, injecting OG data per-route:

// server.js (Express)
import express from 'express';
import { renderFile } from 'ejs';

const app = express();
app.set('view engine', 'ejs');

app.get('/blog/:slug', async (req, res) => {
  const post = await db.getPost(req.params.slug);

  res.render('post', {
    title: post.title,
    description: post.excerpt,
    ogImage: post.ogImage,
    ogUrl: `https://yourdomain.com/blog/${post.slug}`,
  });
});

// views/post.ejs
// <head>
//   <meta property="og:title"       content="<%= title %>" />
//   <meta property="og:description" content="<%= description %>" />
//   <meta property="og:image"       content="<%= ogImage %>" />
//   <meta property="og:url"         content="<%= ogUrl %>" />
// </head>

This is the cleanest pattern for Alpine.js apps with dynamic content: server renders full HTML including OG tags, and Alpine.js enhances behavior without touching the <head>.

OG tag checklist for Alpine.js

  • Never rely on Alpine.js to inject OG tags — crawlers don't run JavaScript.
  • Put all <meta property="og:*"> tags in the server-rendered <head>.
  • For dynamic routes, inject values from your server-side template engine (PHP, EJS, Blade, Nunjucks, etc.).
  • Use absolute URLs for og:image.
  • Image size: 1200×630 px for full-width Twitter/LinkedIn previews.
  • Set twitter:card to summary_large_image.
  • Test each page type (home, blog post, product) with OGFixer.

Test your Alpine.js OG tags for free

Paste any URL into OGFixer to see exactly how your link previews look on Twitter, LinkedIn, Discord, and Slack.

Related Guides