π Read this post in Bahasa Indonesia here
I keep re-looking these up every time I start a new project, so this is where I’m dumping them all in one place β with a short note on why each one matters, so future-me doesn’t have to re-derive it from scratch.
Jump to:
- Document Core and Responsive Setup
- Basic SEO and Indexing
- Social Cards (OG + X)
- GEO / AI Crawlers
- Security Headers
- PWA / Browser
- Quick Table
- What I Actually Use
Document Core and Responsive Setup
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
Enter fullscreen mode Exit fullscreen mode
-
charset="UTF-8"β needs to sit near the top of<head>, or the browser can end up re-parsing the whole doc if it hits non-ASCII bytes first. -
viewportβ without it, mobile just renders the desktop layout zoomed way out. Non-negotiable, always include.
Basic SEO and Indexing
<meta name="description" content="...">
<meta name="robots" content="index, follow">
<link rel="canonical" href="https://sanudin.dev/">
Enter fullscreen mode Exit fullscreen mode
-
descriptionβ the snippet under my link in search results, ~150β160 chars. Doesn’t move ranking directly, but affects click-through. -
robotsβ default is alreadyindex, follow, so I only need this tag when I want the opposite (noindex, nofollowon staging routes). -
canonicalβ points to the “real” URL when the same content is reachable multiple ways (trailing slash, query params, etc).
Social Cards (OG + X)
Open Graph = the universal standard for FB/LinkedIn/Discord/Slack preview cards. X falls back to OG unless I add twitter:* tags specifically.
Image size to remember: 1200Γ630px (1.91:1 ratio).
<meta property="og:type" content="website">
<meta property="og:title" content="Sanudin | Software Engineer">
<meta property="og:description" content="Backend-first software engineer writing about web development, from Indonesia.">
<meta property="og:image" content="https://sanudin.dev/og-cover.png">
<meta property="og:url" content="https://sanudin.dev/">
<meta property="og:site_name" content="Sanudin">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Sanudin | Software Engineer">
<meta name="twitter:description" content="Backend-first software engineer writing about web development, from Indonesia.">
<meta name="twitter:image" content="https://sanudin.dev/og-cover.png">
Enter fullscreen mode Exit fullscreen mode
Reminder: og:type should be article on blog posts, website is only for the homepage/landing pages.
GEO / AI Crawlers
Two separate places to control this β meta tag (page-level) and robots.txt (site-wide).
<meta name="robots" content="noai, noimageai">
Enter fullscreen mode Exit fullscreen mode
User-agent: GPTBot
Disallow: /
User-agent: ClaudeBot
Disallow: /
User-agent: Googlebot
Allow: /
Enter fullscreen mode Exit fullscreen mode
Note to self: noai/noimageai isn’t an official spec tag (started at DeviantArt, 2022) β adoption is spotty at best. robots.txt is the one that actually does something if I care about blocking AI training.
JSON-LD for identity β why I bother with this
This block isn’t for humans, it’s for search engines and AI systems trying to figure out who is talking. A page full of prose saying “I’m Sanudin, a software engineer” is something they have to infer; a JSON-LD block just states it as structured fact.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Person",
"name": "Sanudin",
"jobTitle": "Software Engineer",
"url": "https://sanudin.dev",
"sameAs": [
"https://github.com/sanudin-dev",
"https://linkedin.com/in/sanudin"
],
"knowsAbout": ["TypeScript", "Next.js", "Node.js", "PHP", "Web Development"]
}
</script>
Enter fullscreen mode Exit fullscreen mode
-
@type: "Person"β marks this specifically as data about an entity, not just page content. This is the difference between search engines guessing at who wrote something vs. being told directly. -
sameAsβ this is the field doing the real work. It’s what lets a search engine or AI system connect “Sanudin on GitHub,” “Sanudin on LinkedIn,” and “Sanudin on sanudin.dev” as the same person instead of three unrelated mentions. Without this array, every profile is an island. -
jobTitleβ needs to match my canonical bio wording exactly (“Software Engineer,” not a synonym). Consistency across every profile is literally what builds the entity association β a mismatch works against the whole point of this block. -
knowsAboutβ a topical signal, not keyword stuffing. Should genuinely reflect what the site’s content backs up, or it’s just noise. - Before publishing: run it through Google’s Rich Results Test or the schema.org validator. JSON-LD fails silently β a typo just gets ignored rather than throwing an error I’d notice.
- This is site identity schema. If I want individual posts to carry their own structured data later, that’s a separate
Articletype with anauthorfield pointing back to thisPersonβ a future addition, not something I need to retrofit now.
Security Headers
Content Security Policy β what each part is actually doing
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' https://trustedscripts.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;">
Enter fullscreen mode Exit fullscreen mode
-
default-src 'self'β the baseline: nothing loads unless it’s same-origin. Every other directive below is an exception to this. -
script-src 'self' https://trustedscripts.comβ only my own scripts and this one whitelisted domain can execute. This is the directive that actually matters for stopping injected/XSS scripts β the fact that I haven’t added'unsafe-inline'here is the important part, not an oversight. -
style-src 'self' 'unsafe-inline'β'unsafe-inline'here is a real trade-off, not a default I should ignore. It’s needed because a lot of frameworks injectstyle=""at runtime, but it does weaken protection against injected inline styles specifically (there are real CSS-based UI-redress/exfiltration tricks that rely on exactly this). Much smaller risk than allowing it onscript-src, but if I ever want to close this gap, the fix is nonces or hashes per style block, not just removing the directive. -
img-src 'self' data: https:βdata:allows base64-inlined images (small icons, placeholders),https:broadly allows images from any HTTPS source so I’m not maintaining an allowlist of every image domain.
Reminder on the meta-tag limitation: frame-ancestors, report-uri/report-to, and sandbox don’t work when CSP is set via <meta> β only via an actual HTTP header. The reason is timing: those specific protections need to be enforced before the page starts loading anything, and a meta tag sitting inside the HTML is already too late for that. This meta version is a fallback for when I don’t control server headers (static hosting), not a full substitute.
Referrer Policy β what strict-origin-when-cross-origin actually sends
<meta name="referrer" content="strict-origin-when-cross-origin">
Enter fullscreen mode Exit fullscreen mode
Breaking down what this value actually does, since “strict-origin-when-cross-origin” doesn’t explain itself:
- Same-origin request β sends the full URL (path + query string).
- Cross-origin request, but same protocol security level (https β https) β sends only the origin (scheme + host), not the path.
- Downgrade request (https β http) β sends nothing at all.
Why this matters: without it, clicking an outbound link can leak my site’s internal paths and query strings to whatever site the link points to. Note to self: most modern browsers already default to this exact policy now, so this tag is more about being explicit and guarding against a future default change (or another policy set elsewhere conflicting) than strictly necessary today.
Other values worth remembering if I need something stricter or looser: no-referrer (send nothing, ever β most private, but can break analytics or referrer-based auth flows), same-origin (nothing at all cross-origin), unsafe-url (always send the full URL β avoid, leaks the most).
PWA / Browser
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#09090b">
Enter fullscreen mode Exit fullscreen mode
Colors the browser’s own UI chrome (mobile address bar, OS task-switcher card) to match the page. Two declarations because prefers-color-scheme is a media query β a single tag without it would apply the same color regardless of system theme.
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="Sanudin">
Enter fullscreen mode Exit fullscreen mode
-
mobile-web-app-capableβ puts the page into standalone mode (no URL bar, no browser nav) when launched from the home screen. This is the current, unprefixed tag β Chrome DevTools actively flags the oldapple-mobile-web-app-capableas deprecated now and asks for this version instead. Safari historically only recognized the apple-prefixed one, so I may still need both for older iOS β worth rechecking current Safari support before dropping the legacy tag entirely. Also: as of iOS 26, Safari defaults every home-screen-added site to open in web-app mode regardless, so this tag matters less on iOS than it used to. -
apple-mobile-web-app-status-bar-styleβ still Apple-only, no standardized replacement exists yet.black-translucentlets page content scroll edge-to-edge under the status bar;default/blackkeep a solid bar instead. -
apple-mobile-web-app-titleβ also still Apple-specific, controls the label under the home screen icon. The modern approach is to set this vianame/short_namein manifest.json instead β keeping this meta tag around as a fallback since Safari doesn’t always read the manifest name reliably.
Bigger note to self: the actual current guidance (Chrome/web.dev) is to lean on the Web App Manifest for all of this long-term, not these meta tags β they predate the manifest spec and can produce worse fallback behavior if the manifest fails to load. Treat everything above as a practical compatibility layer, not the ideal end state.
<link rel="manifest" href="/manifest.json">
<link rel="apple-touch-icon" href="/icons/apple-icon-180.png">
<link rel="icon" href="/favicon.ico" sizes="any">
<link rel="icon" href="/icon.svg" type="image/svg+xml">
Enter fullscreen mode Exit fullscreen mode
This is the actual foundation β manifest.json is where name, icons, start_url, display mode, and theme colors are meant to live going forward. apple-touch-icon stays as a separate link because Safari treats it as an override rather than reliably reading icons from the manifest.
Quick Table
Tag Purposecharset
Correct character encoding
viewport
Responsive mobile rendering
description
SERP snippet text
robots
Index/crawl instructions
canonical
Avoid duplicate-content issues
og:*
Preview cards (FB, LinkedIn, Discord)
twitter:*
Preview cards on X
robots: noai, noimageai
Voluntary AI-training opt-out signal
robots.txt (GPTBot, etc.)
Site-wide AI crawler blocking
JSON-LD (Person)
Structured identity data for AI/search
Content-Security-Policy
Restrict allowed script/style/media sources
referrer
Control outbound referrer data
theme-color
Mobile browser chrome color
mobile-web-app-capable
Standalone launch mode (current tag)
apple-mobile-web-app-*
iOS-specific home screen behavior
manifest
PWA installability
What I Actually Use
Always: charset, viewport, description, canonical, og:/twitter:.
Once the basics are set: robots (only when I actually need noindex), theme-color, PWA tags, CSP.
Situational, revisit if it matters that day: GEO tags, JSON-LD.
Originally published at: HTML Meta Tags: My Reference Notes
λ΅κΈ λ¨κΈ°κΈ°
λκΈμ λ¬κΈ° μν΄μλ λ‘κ·ΈμΈν΄μΌν©λλ€.