SyntaxStudy
Sign Up
Next.js Icons, Manifest, and Structured Data
Next.js Beginner 1 min read

Icons, Manifest, and Structured Data

Next.js handles favicon and app icons through file conventions. Placing a favicon.ico, icon.png, apple-icon.png, or icon.svg file in the app/ directory automatically adds the appropriate link tags to the document head. For programmatically generated icons, you can create icon.tsx and apple-icon.tsx files that export ImageResponse functions, similar to OG images. A web app manifest (manifest.json) enables Progressive Web App features like "Add to Home Screen". Next.js generates the manifest automatically when you create app/manifest.ts and export a WebManifest object. The manifest can reference icons defined in your app/ directory. Setting the display property to 'standalone' makes the app open without browser UI when launched from the home screen. Structured data (JSON-LD) helps search engines understand your content and can enable rich results in Google Search. Since Next.js metadata does not have a built-in JSON-LD field, you inject it via a script tag inside your page or layout component. Using the type="application/ld+json" attribute and dangerouslySetInnerHTML to embed a JSON-LD script is the recommended approach — it is safe because the data is serialised and the script type prevents execution.
Example
// app/manifest.ts
import type { MetadataRoute } from 'next';

export default function manifest(): MetadataRoute.Manifest {
  return {
    name: 'Acme App',
    short_name: 'Acme',
    description: 'Build faster with Acme',
    start_url: '/',
    display: 'standalone',
    background_color: '#ffffff',
    theme_color: '#0f172a',
    icons: [
      { src: '/icon-192.png', sizes: '192x192', type: 'image/png' },
      { src: '/icon-512.png', sizes: '512x512', type: 'image/png' },
    ],
  };
}

// app/blog/[slug]/page.tsx — JSON-LD structured data
interface Post {
  slug: string;
  title: string;
  excerpt: string;
  author: string;
  publishedAt: string;
  coverImageUrl: string;
}

function ArticleJsonLd({ post }: { post: Post }) {
  const jsonLd = {
    '@context': 'https://schema.org',
    '@type': 'Article',
    headline: post.title,
    description: post.excerpt,
    image: post.coverImageUrl,
    author: { '@type': 'Person', name: post.author },
    datePublished: post.publishedAt,
    publisher: {
      '@type': 'Organization',
      name: 'Acme',
      logo: { '@type': 'ImageObject', url: 'https://acme.com/logo.png' },
    },
  };

  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
    />
  );
}

export default async function BlogPostPage({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug);
  return (
    <>
      <ArticleJsonLd post={post} />
      <article>
        <h1>{post.title}</h1>
      </article>
    </>
  );
}