OneMinuteBrandingOneMinuteBrandingGenerate Brand
  1. Home
  2. Blog
  3. The Complete Guide to Favicons, OG Images, and App Icons in 2026
faviconog imageSEOdevelopers

The Complete Guide to Favicons, OG Images, and App Icons in 2026

17 different icon sizes, OG images, Apple touch icons. Here's every size you need, where each one goes, and how to generate them all from one source.

March 16, 20269 min readBy Yann Lephay

You just pushed your Next.js app to production. Vercel gives you the green checkmark. You drop the link in your team's Discord. Instead of a polished preview, Discord unfurls a gray globe icon and a generic text snippet. Your app looks like a phishing site. You open the site on your iPhone, click "Add to Home Screen," and get a blurry, auto-generated screenshot of your hero section instead of a proper app icon.

You spend the next three hours resizing PNGs in Figma, writing HTML <meta> tags you don't understand, and fighting aggressive browser caching just to make a 32x32 pixel square show up in a Chrome tab.

The Legacy Icon Headache

Historically, supporting every browser and device meant generating over 30 different icon files. You needed a favicon-16x16.png, a favicon-32x32.png, specific dimensions for Windows 8 Metro tiles (mstile-144x144.png), and Safari pinned tab SVG masks.

In 2026, browser vendors have finally aligned on modern standards, but the legacy cruft remains in most boilerplate templates. You do not need 30 files. If you are still generating 16x16 PNGs, you are wasting bytes and time. Modern browsers scale SVGs perfectly.

However, Apple and Google still demand specific rasterized PNGs for their home screens and progressive web app (PWA) manifests. You can't just ship an SVG and call it a day. iOS will ignore your SVG and render a blank square on the home screen.

The 2026 Icon Checklist

You only need exactly 6 files to achieve 100% coverage across modern browsers, iOS home screens, Android PWAs, and social media unfurls.

File NameDimensionsFormatPurpose
favicon.ico32x32ICOLegacy fallback for RSS readers and old browsers.
icon.svgAnySVGThe modern standard for all desktop browsers.
apple-touch-icon.png180x180PNGiOS Home Screen. Must have a solid background (no transparency).
web-app-manifest-192.png192x192PNGAndroid PWA icon. Used by Chrome for Android.
web-app-manifest-512.png512x512PNGAndroid PWA splash screen. Required for Lighthouse PWA score.
og-image.jpg1200x630JPGSocial media unfurls (Twitter, Slack, Discord).

Delete safari-pinned-tab.svg. Apple deprecated it in macOS Safari 12. Delete browserconfig.xml. Windows 11 no longer uses live tiles. Stick to these 6 files.

The Required HTML Head Tags

Drop this exact block into your root layout. Do not add redundant tags.

Code
<!-- Modern SVG Favicon -->
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
<!-- Legacy Fallback -->
<link rel="icon" href="/favicon.ico" sizes="32x32" />
<!-- iOS Home Screen -->
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<!-- Android PWA Manifest -->
<link rel="manifest" href="/manifest.json" />
 
<!-- Open Graph / Social -->
<meta property="og:type" content="website" />
<meta property="og:title" content="Your App Name" />
<meta property="og:description" content="Your app description." />
<meta property="og:image" content="https://yourdomain.com/og-image.jpg" />
<meta name="twitter:card" content="summary_large_image" />
## Generating Everything From One SVG
 
Never export these 6 files manually from Figma. Every time you tweak your brand color by one hex code, you will have to re-export, rename, and move 6 files into your `public` directory.
 
You have two options to automate this.
 
**Option 1: The Node.js + Sharp Script**
Write a script that takes a single `source-icon.svg` and generates the required PNGs and ICO files during your build step. You will need the `sharp` and `png-to-ico` packages.
 
```typescript
// scripts/generate-icons.ts
import sharp from 'sharp';
import pngToIco from 'png-to-ico';
import { readFileSync, writeFileSync } from 'fs';
 
const sourceSvg = './design/source-icon.svg';
const publicDir = './public';
 
async function generateIcons() {
  const svgBuffer = readFileSync(sourceSvg);
 
  // 1. Generate 180x180 Apple Touch Icon (Solid white background required by Apple)
  await sharp(svgBuffer)
    .resize(180, 180)
    .flatten({ background: '#ffffff' }) 
    .toFile(`${publicDir}/apple-touch-icon.png`);
 
  // 2. Generate PWA Manifest Icons (Transparent background allowed)
  await sharp(svgBuffer).resize(192, 192).toFile(`${publicDir}/web-app-manifest-192.png`);
  await sharp(svgBuffer).resize(512, 512).toFile(`${publicDir}/web-app-manifest-512.png`);
 
  // 3. Generate legacy 32x32 favicon.ico
  const tempPng = await sharp(svgBuffer).resize(32, 32).toBuffer();
  const icoBuffer = await pngToIco(tempPng);
  writeFileSync(`${publicDir}/favicon.ico`, icoBuffer);
  
  // 4. Copy the raw SVG
  writeFileSync(`${publicDir}/icon.svg`, svgBuffer);
}
 
generateIcons().catch(console.error);

Option 2: OneMinuteBranding If you don't want to maintain an image processing script in your repository, use OneMinuteBranding. For a $49 one-time payment, it generates your entire brand identity in 60 seconds. You input your product name, and it outputs a .zip containing your Tailwind config, CSS variables (complete 7-color scales from 50 to 950), a CLAUDE.md context file, and a folder containing these exact 6 pre-sized icons. You drag the public folder into your repo and never think about it again.

Apple Touch Icons and the Web App Manifest

Apple iOS aggressively ignores the web standards defined in your manifest.json. If a user taps "Add to Home Screen" on an iPhone, iOS specifically looks for the <link rel="apple-touch-icon"> tag.

If you serve a transparent PNG to iOS, Apple will fill the transparent pixels with solid black. If your logo is dark, you will end up with a black-on-black unreadable square. You must explicitly flatten your apple-touch-icon.png onto a solid background color (usually white or your brand's primary 900 shade).

Additionally, iOS applies a "squircle" mask to your icon. If your logo stretches to the absolute edge of the 180x180 canvas, the corners will be chopped off. Always leave a 20px padding margin inside your source SVG.

For Android, Chrome relies entirely on the manifest.json file. Create this file in your public directory:

Code
{
  "name": "Your App Name",
  "short_name": "App",
  "description": "Your app description goes here.",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#0f172a",
  "icons": [
    {
      "src": "/web-app-manifest-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/web-app-manifest-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

Lighthouse specifically checks for the 192x192 and 512x512 sizes in this exact format. Missing either will drop your PWA score and prevent the "Install App" prompt from firing in Chrome.

Open Graph Images: Static vs Dynamic

When you link your site in Slack, Discord, or Twitter, their bots scrape your <meta property="og:image"> tag. The standard dimension is 1200x630 pixels.

Use JPEG, not PNG, for your static OG images. A 1200x630 PNG with any photographic or gradient element will easily exceed 1MB. Slack will timeout while trying to download it, resulting in a broken unfurl. A 1200x630 JPEG saved at 80% quality is usually under 150KB and loads instantly.

For a landing page, a static JPEG is fine. For anything with dynamic content—a blog, a documentation site, or public user profiles—static images are a dead end. Do not attempt to use Puppeteer to take headless browser screenshots of your pages at build time. It is slow, brittle, and will double your CI/CD pipeline duration.

Use @vercel/og. It uses Satori, an engine that converts HTML and CSS into SVG, and then rasterizes it to PNG at the edge in single-digit milliseconds.

Next.js App Router integrates this natively via the opengraph-image.tsx file convention.

Code
// app/blog/[slug]/opengraph-image.tsx
import { ImageResponse } from 'next/og';
 
export const runtime = 'edge';
export const alt = 'Blog Post Image';
export const size = { width: 1200, height: 630 };
 
export default async function Image({ params }: { params: { slug: string } }) {
  // Fetch your blog post data
  const post = await getPostBySlug(params.slug);
 
  return new ImageResponse(
    (
      <div
        style={{
          background: 'linear-gradient(to right, #0f172a, #1e293b)',
          width: '100%',
          height: '100%',
          display: 'flex',
          flexDirection: 'column',
          justifyContent: 'center',
          padding: '80px',
        }}
      >
        <h1 style={{ fontSize: 80, color: 'white', fontWeight: 800, margin: 0 }}>
          {post.title}
        </h1>
        <p style={{ fontSize: 40, color: '#94a3b8', marginTop: 40 }}>
          {post.author} • {post.readingTime} min read
        </p>
      </div>
    ),
    { ...size }
  );
}

Satori does not support CSS Grid, calc(), or complex pseudo-elements. You must use basic Flexbox and inline styles. The tradeoff is worth it: you get zero-maintenance, dynamic OG images that generate on the fly and cache automatically.

Cache Busting Your Icons

Favicons are the most aggressively cached assets on the web. Chrome will cache a favicon.ico for up to 30 days. If you deploy a new logo and just replace the file in your public directory, you will see the old logo in your tab for a month, and you will waste hours debugging your deployment.

Never rely on standard HTTP cache headers for favicons. Use query string cache busting directly in your HTML tags.

Code
<link rel="icon" type="image/svg+xml" href="/icon.svg?v=2" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png?v=2" />

When you update your brand, bump the ?v= parameter. The browser interprets this as a completely new asset and fetches it immediately.

FAQ

How do I implement a dark mode favicon?

Because modern browsers support SVG favicons, you can write CSS media queries directly inside your icon.svg file. You don't need JavaScript or multiple link tags to swap icons based on system theme.

Code
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
  <style>
    .bg { fill: #ffffff; }
    .mark { fill: #0f172a; }
    @media (prefers-color-scheme: dark) {
      .bg { fill: #0f172a; }
      .mark { fill: #ffffff; }
    }
  </style>
  <rect class="bg" width="100" height="100" rx="20" />
  <path class="mark" d="M30 30 h40 v40 h-40 z" />
</svg>

When the user toggles their OS to dark mode, the SVG instantly updates in the browser tab. Note: This only works for the icon.svg. The apple-touch-icon.png and manifest.json icons are rasterized and do not support dynamic theming.

Can I just use an emoji as my favicon?

Yes. During early development, generating brand assets is a distraction. You can embed an emoji directly into an SVG data URI in your <link> tag.

Code
<link 
  rel="icon" 
  href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🚀</text></svg>" 
/>

This requires zero files in your public directory. Replace it with your actual 6-file setup before you launch to production.

Why is my OG image not updating on Twitter/Slack?

Social media platforms cache OG images on their own servers the first time a URL is shared. Updating your og-image.jpg or bumping the ?v=2 cache buster will not clear Twitter's backend cache.

To force a refresh, you must run your URL through the specific platform's debugging tool. Paste your production URL into the Meta Sharing Debugger and click "Scrape Again". For LinkedIn, use the Post Inspector. Twitter recently removed their Card Validator tool, so the only way to bust Twitter's cache is to append a dummy query parameter to the URL you share in your tweet (e.g., https://yourapp.com/?ref=1).

Open your app/layout.tsx file right now. Delete safari-pinned-tab.svg, browserconfig.xml, and the 16x16 PNGs. Add the 6 required files. Append ?v=1 to every href attribute in your meta tags, commit the changes, and deploy.

Y
Yann Lephay@YannBuilds

Vibe coder & Indie Hacker. Building tools to help devs ship faster. Creator of OneMinuteBranding.

Ready to create your brand?

Generate a complete brand system with Tailwind config in 60 seconds.

Generate your brand

Related articles

The Developer's Brand Kit Checklist: 23 Files You Need Before Launch

Before you launch, make sure you have these 23 files. From tailwind.config.ts to OG images to CLAUDE.md. Copy this checklist.

Font Pairing for Developers: 7 Combinations That Always Work

Stop spending 2 hours on Google Fonts. These 7 font pairs work for any SaaS product. With next/font code snippets.

Branding Your Open Source Project: From Zero to Professional

Your README has 5 stars because it looks like a homework assignment. Professional branding makes open source projects 3x more likely to get adopted.

Explore more

Branding by RoleBranding by IndustryUse CasesFeaturesIntegrationsGlossaryFree Tools
BlogAboutTermsPrivacy