I don't want the title to be products always, I want it to be All Products by default if title searchParam is not provided otherwise it should be title searchParam... Also improve the seo even more... our websites called "naayiq.com" it is a website for selling global and korean brands for makeups, skin care and a lot of other categories...
[{"id":2,"name":"Body Care","is_brand":false},{"id":4,"name":"Eye Care","is_brand":false},{"id":1,"name":"Face Care","is_brand":false},{"id":22,"name":"Foot care","is_brand":false},{"id":11,"name":"Global Brands","is_brand":true},{"id":3,"name":"Hair Care","is_brand":false},{"id":6,"name":"Hand Care","is_brand":false},{"id":9,"name":"Kit","is_brand":false},{"id":10,"name":"Korean Brands","is_brand":true},{"id":5,"name":"Lip Care","is_brand":false},{"id":7,"name":"Makeup","is_brand":false},{"id":23,"name":"Mouth Care","is_brand":false},{"id":8,"name":"Perfume","is_brand":false}]
[{"id":178,"name":"Body Cleansers","category_id":2,"category_name":"Body Care"},{"id":184,"name":"Body Exfoliating Solutions","category_id":2,"category_name":"Body Care"},{"id":179,"name":"Body Moisturizers","category_id":2,"category_name":"Body Care"},{"id":181,"name":"Body Sunscreens","category_id":2,"category_name":"Body Care"},{"id":213,"name":"Body mist","category_id":2,"category_name":"Body Care"},{"id":212,"name":"Body oil","category_id":2,"category_name":"Body Care"},{"id":269,"name":"Body razors","category_id":2,"category_name":"Body Care"},{"id":210,"name":"Body scrub","category_id":2,"category_name":"Body Care"},{"id":182,"name":"Deodorant","category_id":2,"category_name":"Body Care"},{"id":180,"name":"Fragrance","category_id":2,"category_name":"Body Care"},{"id":183,"name":"Tools","category_id":2,"category_name":"Body Care"},{"id":202,"name":"hair removal","category_id":2,"category_name":"Body Care"},{"id":159,"name":"Eye Cream","category_id":4,"category_name":"Eye Care"},{"id":160,"name":"Eye Masks","category_id":4,"category_name":"Eye Care"},{"id":196,"name":"Eye Patch","category_id":4,"category_name":"Eye Care"},{"id":157,"name":"Eye Serum","category_id":4,"category_name":"Eye Care"},{"id":161,"name":"Eye Treatments","category_id":4,"category_name":"Eye Care"},{"id":158,"name":"Lash Serum","category_id":4,"category_name":"Eye Care"},{"id":192,"name":"Cleansing balm","category_id":1,"category_name":"Face Care"},{"id":191,"name":"Cleansing oil","category_id":1,"category_name":"Face Care"},{"id":197,"name":"Essence","category_id":1,"category_name":"Face Care"},{"id":168,"name":"Face Cleansers","category_id":1,"category_name":"Face Care"},{"id":177,"name":"Face Exfoliating Acids","category_id":1,"category_name":"Face Care"},{"id":173,"name":"Face Sunscreens","category_id":1,"category_name":"Face Care"},{"id":172,"name":"Face moisturiser","category_id":1,"category_name":"Face Care"},{"id":174,"name":"Facial Oils","category_id":1,"category_name":"Face Care"},{"id":175,"name":"Masks","category_id":1,"category_name":"Face Care"},{"id":198,"name":"Micellar","category_id":1,"category_name":"Face Care"},{"id":193,"name":"Mist","category_id":1,"category_name":"Face Care"},{"id":195,"name":"Patch","category_id":1,"category_name":"Face Care"},{"id":194,"name":"Scrub","category_id":1,"category_name":"Face Care"},{"id":170,"name":"Serums","category_id":1,"category_name":"Face Care"},{"id":217,"name":"Spot","category_id":1,"category_name":"Face Care"},{"id":190,"name":"Toner pads","category_id":1,"category_name":"Face Care"},{"id":169,"name":"Toners","category_id":1,"category_name":"Face Care"},{"id":235,"name":"Tools","category_id":1,"category_name":"Face Care"},{"id":176,"name":"Treatments","category_id":1,"category_name":"Face Care"},{"id":209,"name":"Foot moisturizers","category_id":22,"category_name":"Foot care"},{"id":73,"name":"Acm","category_id":11,"category_name":"Global Brands"},{"id":95,"name":"Acure","category_id":11,"category_name":"Global Brands"},{"id":88,"name":"Advanced Clinicals","category_id":11,"category_name":"Global Brands"},{"id":91,"name":"Arm & Hammer","category_id":11,"category_name":"Global Brands"},{"id":301,"name":"Aveda","category_id":11,"category_name":"Global Brands"},{"id":86,"name":"Aveeno","category_id":11,"category_name":"Global Brands"},{"id":78,"name":"Avene","category_id":11,"category_name":"Global Brands"},{"id":96,"name":"Bath And Body","category_id":11,"category_name":"Global Brands"},{"id":309,"name":"Bio oil","category_id":11,"category_name":"Global Brands"},{"id":76,"name":"Bioderma","category_id":11,"category_name":"Global Brands"},{"id":200,"name":"Bionnex","category_id":11,"category_name":"Global Brands"},{"id":273,"name":"Biore","category_id":11,"category_name":"Global Brands"},{"id":15,"name":"Briogio","category_id":11,"category_name":"Global Brands"},{"id":103,"name":"Cantu","category_id":11,"category_name":"Global Brands"},{"id":11,"name":"CeraVe","category_id":11,"category_name":"Global Brands"},{"id":12,"name":"Cetaphil","category_id":11,"category_name":"Global Brands"},{"id":228,"name":"Clarins","category_id":11,"category_name":"Global Brands"},{"id":64,"name":"Clinique","category_id":11,"category_name":"Global Brands"},{"id":215,"name":"Color wow","category_id":11,"category_name":"Global Brands"},{"id":246,"name":"Cotton","category_id":11,"category_name":"Global Brands"},{"id":225,"name":"Crest","category_id":11,"category_name":"Global Brands"},{"id":89,"name":"Dermedic","category_id":11,"category_name":"Global Brands"},{"id":240,"name":"Drunk Elephant","category_id":11,"category_name":"Global Brands"},{"id":274,"name":"Ecotools","category_id":11,"category_name":"Global Brands"},{"id":307,"name":"Ecrinal","category_id":11,"category_name":"Global Brands"},{"id":97,"name":"Embryolisse","category_id":11,"category_name":"Global Brands"},{"id":104,"name":"Eucerin","category_id":11,"category_name":"Global Brands"},{"id":206,"name":"Eveline","category_id":11,"category_name":"Global Brands"},{"id":265,"name":"Fable & Mane","category_id":11,"category_name":"Global Brands"},{"id":63,"name":"Farmacy","category_id":11,"category_name":"Global Brands"},{"id":218,"name":"First aid beauty","category_id":11,"category_name":"Global Brands"},{"id":80,"name":"Floxia","category_id":11,"category_name":"Global Brands"},{"id":93,"name":"Foltene","category_id":11,"category_name":"Global Brands"},{"id":81,"name":"Garnier","category_id":11,"category_name":"Global Brands"},{"id":270,"name":"Gillette Venus","category_id":11,"category_name":"Global Brands"},{"id":13,"name":"Gisou","category_id":11,"category_name":"Global Brands"},{"id":14,"name":"Glow Recipe","category_id":11,"category_name":"Global Brands"},{"id":68,"name":"Grande Cosmetics","category_id":11,"category_name":"Global Brands"},{"id":94,"name":"Isdin","category_id":11,"category_name":"Global Brands"},{"id":74,"name":"Isis Pharma","category_id":11,"category_name":"Global Brands"},{"id":107,"name":"K18","category_id":11,"category_name":"Global Brands"},{"id":62,"name":"Kerastase","category_id":11,"category_name":"Global Brands"},{"id":70,"name":"La Roche","category_id":11,"category_name":"Global Brands"},{"id":263,"name":"Laneige","category_id":11,"category_name":"Global Brands"},{"id":222,"name":"Loreal Paris","category_id":11,"category_name":"Global Brands"},{"id":308,"name":"Mavala","category_id":11,"category_name":"Global Brands"},{"id":77,"name":"Mielle","category_id":11,"category_name":"Global Brands"},{"id":10,"name":"Morphe","category_id":11,"category_name":"Global Brands"},{"id":208,"name":"NIVEA","category_id":11,"category_name":"Global Brands"},{"id":65,"name":"Naturium","category_id":11,"category_name":"Global Brands"},{"id":87,"name":"Neutrogena","category_id":11,"category_name":"Global Brands"},{"id":72,"name":"Noreva","category_id":11,"category_name":"Global Brands"},{"id":305,"name":"OKeeffes","category_id":11,"category_name":"Global Brands"},{"id":106,"name":"Ogx","category_id":11,"category_name":"Global Brands"},{"id":105,"name":"Olaplex","category_id":11,"category_name":"Global Brands"},{"id":18,"name":"Ole Henriksen","category_id":11,"category_name":"Global Brands"},{"id":98,"name":"Ordinary","category_id":11,"category_name":"Global Brands"},{"id":60,"name":"Origins","category_id":11,"category_name":"Global Brands"},{"id":216,"name":"Oz naturals","category_id":11,"category_name":"Global Brands"},{"id":199,"name":"Palmers","category_id":11,"category_name":"Global Brands"},{"id":101,"name":"Panoxyl","category_id":11,"category_name":"Global Brands"},{"id":59,"name":"Paulas Choice","category_id":11,"category_name":"Global Brands"},{"id":84,"name":"Pfb","category_id":11,"category_name":"Global Brands"},{"id":17,"name":"Pixi","category_id":11,"category_name":"Global Brands"},{"id":261,"name":"Qv","category_id":11,"category_name":"Global Brands"},{"id":304,"name":"Ren Clean","category_id":11,"category_name":"Global Brands"},{"id":230,"name":"Revitalash","category_id":11,"category_name":"Global Brands"},{"id":61,"name":"Sephora","category_id":11,"category_name":"Global Brands"},{"id":82,"name":"Sesderma","category_id":11,"category_name":"Global Brands"},{"id":69,"name":"Shiseido","category_id":11,"category_name":"Global Brands"},{"id":204,"name":"Simple","category_id":11,"category_name":"Global Brands"},{"id":268,"name":"Softymo","category_id":11,"category_name":"Global Brands"},{"id":16,"name":"Sol De Janeiro","category_id":11,"category_name":"Global Brands"},{"id":300,"name":"Sunday riley","category_id":11,"category_name":"Global Brands"},{"id":67,"name":"Supergoop!","category_id":11,"category_name":"Global Brands"},{"id":75,"name":"Svr","category_id":11,"category_name":"Global Brands"},{"id":272,"name":"The Breath Co.","category_id":11,"category_name":"Global Brands"},{"id":233,"name":"The Purest Solutions","category_id":11,"category_name":"Global Brands"},{"id":203,"name":"The inkey list","category_id":11,"category_name":"Global Brands"},{"id":90,"name":"Tizo","category_id":11,"category_name":"Global Brands"},{"id":92,"name":"Tree Hut","category_id":11,"category_name":"Global Brands"},{"id":85,"name":"Uriage","category_id":11,"category_name":"Global Brands"},{"id":79,"name":"Vaseline","category_id":11,"category_name":"Global Brands"},{"id":83,"name":"Veet","category_id":11,"category_name":"Global Brands"},{"id":71,"name":"Vichy","category_id":11,"category_name":"Global Brands"},{"id":303,"name":"White Glo","category_id":11,"category_name":"Global Brands"},{"id":264,"name":"Wishful","category_id":11,"category_name":"Global Brands"},{"id":108,"name":"Wow Color","category_id":11,"category_name":"Global Brands"},{"id":66,"name":"Youth To The People","category_id":11,"category_name":"Global Brands"},{"id":207,"name":"garnier","category_id":11,"category_name":"Global Brands"},{"id":231,"name":"kiehl's","category_id":11,"category_name":"Global Brands"},{"id":211,"name":"‏ Victorias Secret","category_id":11,"category_name":"Global Brands"},{"id":221,"name":"‏Coco Monkey","category_id":11,"category_name":"Global Brands"},{"id":201,"name":"‏Galenia Skin Care","category_id":11,"category_name":"Global Brands"},{"id":223,"name":"‏Good Molecules","category_id":11,"category_name":"Global Brands"},{"id":310,"name":"‏Rapidlash","category_id":11,"category_name":"Global Brands"},{"id":248,"name":"‏Shea Moisture","category_id":11,"category_name":"Global Brands"},{"id":2,"name":"Conditioners","category_id":3,"category_name":"Hair Care"},{"id":3,"name":"Dry Shampoos","category_id":3,"category_name":"Hair Care"},{"id":7,"name":"Hair Brush","category_id":3,"category_name":"Hair Care"},{"id":6,"name":"Hair Mist","category_id":3,"category_name":"Hair Care"},{"id":4,"name":"Hair Oils","category_id":3,"category_name":"Hair Care"},{"id":8,"name":"Hair Styling","category_id":3,"category_name":"Hair Care"},{"id":9,"name":"Hair Treatments","category_id":3,"category_name":"Hair Care"},{"id":220,"name":"Heat protectant","category_id":3,"category_name":"Hair Care"},{"id":219,"name":"Leave-In","category_id":3,"category_name":"Hair Care"},{"id":5,"name":"Masks","category_id":3,"category_name":"Hair Care"},{"id":227,"name":"Serum","category_id":3,"category_name":"Hair Care"},{"id":1,"name":"Shampoos","category_id":3,"category_name":"Hair Care"},{"id":236,"name":"Tools","category_id":3,"category_name":"Hair Care"},{"id":163,"name":"Hand moisturiser","category_id":6,"category_name":"Hand Care"},{"id":165,"name":"Nail Polish","category_id":6,"category_name":"Hand Care"},{"id":166,"name":"Nail Polish Remover","category_id":6,"category_name":"Hand Care"},{"id":167,"name":"Nail Treatments","category_id":6,"category_name":"Hand Care"},{"id":109,"name":"Face Kit","category_id":9,"category_name":"Kit"},{"id":141,"name":"Abib","category_id":10,"category_name":"Korean Brands"},{"id":117,"name":"Anua","category_id":10,"category_name":"Korean Brands"},{"id":260,"name":"Aromatica","category_id":10,"category_name":"Korean Brands"},{"id":116,"name":"Axis Y","category_id":10,"category_name":"Korean Brands"},{"id":115,"name":"Beauty Of Joseon","category_id":10,"category_name":"Korean Brands"},{"id":257,"name":"Cos De Baha","category_id":10,"category_name":"Korean Brands"},{"id":119,"name":"Cosrx","category_id":10,"category_name":"Korean Brands"},{"id":126,"name":"Coxir","category_id":10,"category_name":"Korean Brands"},{"id":129,"name":"Dr.Althea","category_id":10,"category_name":"Korean Brands"},{"id":137,"name":"Dr.Jart+","category_id":10,"category_name":"Korean Brands"},{"id":241,"name":"Dr.g","category_id":10,"category_name":"Korean Brands"},{"id":125,"name":"Elizavecca","category_id":10,"category_name":"Korean Brands"},{"id":120,"name":"Etude House","category_id":10,"category_name":"Korean Brands"},{"id":139,"name":"Goodal","category_id":10,"category_name":"Korean Brands"},{"id":127,"name":"Haru Haru","category_id":10,"category_name":"Korean Brands"},{"id":122,"name":"Im From","category_id":10,"category_name":"Korean Brands"},{"id":142,"name":"Innisfree","category_id":10,"category_name":"Korean Brands"},{"id":118,"name":"Isntree","category_id":10,"category_name":"Korean Brands"},{"id":262,"name":"Iunik","category_id":10,"category_name":"Korean Brands"},{"id":130,"name":"Jumiso","category_id":10,"category_name":"Korean Brands"},{"id":245,"name":"Kaine","category_id":10,"category_name":"Korean Brands"},{"id":123,"name":"Laneige","category_id":10,"category_name":"Korean Brands"},{"id":134,"name":"Manyo","category_id":10,"category_name":"Korean Brands"},{"id":244,"name":"Missha","category_id":10,"category_name":"Korean Brands"},{"id":138,"name":"Mixsoon","category_id":10,"category_name":"Korean Brands"},{"id":143,"name":"Neogen","category_id":10,"category_name":"Korean Brands"},{"id":133,"name":"Nine Less","category_id":10,"category_name":"Korean Brands"},{"id":132,"name":"Numbuzin","category_id":10,"category_name":"Korean Brands"},{"id":205,"name":"Purito","category_id":10,"category_name":"Korean Brands"},{"id":239,"name":"Purito Seoul","category_id":10,"category_name":"Korean Brands"},{"id":121,"name":"Round Lab","category_id":10,"category_name":"Korean Brands"},{"id":267,"name":"Sccret key","category_id":10,"category_name":"Korean Brands"},{"id":247,"name":"Skin food","category_id":10,"category_name":"Korean Brands"},{"id":128,"name":"Skin1004","category_id":10,"category_name":"Korean Brands"},{"id":136,"name":"Snow White","category_id":10,"category_name":"Korean Brands"},{"id":131,"name":"Some By Mi","category_id":10,"category_name":"Korean Brands"},{"id":251,"name":"Tiam","category_id":10,"category_name":"Korean Brands"},{"id":140,"name":"Tirtir","category_id":10,"category_name":"Korean Brands"},{"id":124,"name":"Tocobo","category_id":10,"category_name":"Korean Brands"},{"id":135,"name":"Torriden","category_id":10,"category_name":"Korean Brands"},{"id":153,"name":"Lip Balm","category_id":5,"category_name":"Lip Care"},{"id":155,"name":"Lip Exfoliating","category_id":5,"category_name":"Lip Care"},{"id":156,"name":"Lip Mask","category_id":5,"category_name":"Lip Care"},{"id":154,"name":"Lip Oil","category_id":5,"category_name":"Lip Care"},{"id":148,"name":"Cheeks","category_id":7,"category_name":"Makeup"},{"id":145,"name":"Eyebrows","category_id":7,"category_name":"Makeup"},{"id":146,"name":"Eyes","category_id":7,"category_name":"Makeup"},{"id":144,"name":"Face","category_id":7,"category_name":"Makeup"},{"id":147,"name":"Lips","category_id":7,"category_name":"Makeup"},{"id":149,"name":"Makeup Brush","category_id":7,"category_name":"Makeup"},{"id":271,"name":"Mouth freshener","category_id":23,"category_name":"Mouth Care"},{"id":226,"name":"Teeth","category_id":23,"category_name":"Mouth Care"},{"id":306,"name":"Tooth Paste","category_id":23,"category_name":"Mouth Care"},{"id":151,"name":"Body Mist","category_id":8,"category_name":"Perfume"},{"id":152,"name":"Candles","category_id":8,"category_name":"Perfume"},{"id":150,"name":"Hair Mist","category_id":8,"category_name":"Perfume"}]
products/page.jsx:
// /app/products/page.jsx

import React from 'react';
import ProductList from '@/components/ProductList';
import AsyncNavBar from '@/components/AsyncNavBar';

export const revalidate = 14400;
export const metadata = {
    title: "Products",
    description: "Explore our wide range of Korean and global beauty products. Find skincare, makeup, and body care items from top brands at Naay.",
    openGraph: {
        title: "Products",
        description: "Discover premium beauty products from Korean and global brands at Naay. Your trusted source for skincare, makeup, and body care in Iraq.",
    },
};
const ProductsPage = async ({ searchParams }) => {
    const resolvedSearchParams = await searchParams;

    const { c = '', sc = '', b = '', title = '', sortBy = '' } = await resolvedSearchParams;
    return (
        <>
            <AsyncNavBar />
            <ProductList initialFilters={{c, sc, b, title, sortBy}} />
        </>
    );
};

export default ProductsPage;
Docs:

nextjs.org
Optimizing: Metadata | Next.js
9–11 minutes

Next.js has a Metadata API that can be used to define your application metadata (e.g. meta and link tags inside your HTML head element) for improved SEO and web shareability.

There are two ways you can add metadata to your application:

    Config-based Metadata: Export a static metadata object or a dynamic generateMetadata function in a layout.js or page.js file.
    File-based Metadata: Add static or dynamically generated special files to route segments.

With both these options, Next.js will automatically generate the relevant <head> elements for your pages. You can also create dynamic OG images using the ImageResponse constructor.

To define static metadata, export a Metadata object from a layout.js or static page.js file.

import type { Metadata } from 'next'
 
export const metadata: Metadata = {
  title: '...',
  description: '...',
}
 
export default function Page() {}

For all the available options, see the API Reference.

You can use generateMetadata function to fetch metadata that requires dynamic values.

import type { Metadata, ResolvingMetadata } from 'next'
 
type Props = {
  params: { id: string }
  searchParams: { [key: string]: string | string[] | undefined }
}
 
export async function generateMetadata(
  { params, searchParams }: Props,
  parent: ResolvingMetadata
): Promise<Metadata> {
  // read route params
  const id = params.id
 
  // fetch data
  const product = await fetch(`https://.../${id}`).then((res) => res.json())
 
  // optionally access and extend (rather than replace) parent metadata
  const previousImages = (await parent).openGraph?.images || []
 
  return {
    title: product.title,
    openGraph: {
      images: ['/some-specific-page-image.jpg', ...previousImages],
    },
  }
}
 
export default function Page({ params, searchParams }: Props) {}

For all the available params, see the API Reference.

    Good to know:

        Both static and dynamic metadata through generateMetadata are only supported in Server Components.
        fetch requests are automatically memoized for the same data across generateMetadata, generateStaticParams, Layouts, Pages, and Server Components. React cache can be used if fetch is unavailable.
        Next.js will wait for data fetching inside generateMetadata to complete before streaming UI to the client. This guarantees the first part of a streamed response includes <head> tags.

These special files are available for metadata:

    favicon.ico, apple-icon.jpg, and icon.jpg
    opengraph-image.jpg and twitter-image.jpg
    robots.txt
    sitemap.xml

You can use these for static metadata, or you can programmatically generate these files with code.

For implementation and examples, see the Metadata Files API Reference and Dynamic Image Generation.
Behavior

File-based metadata has the higher priority and will override any config-based metadata.
Default Fields

There are two default meta tags that are always added even if a route doesn't define metadata:

    The meta charset tag

sets the character encoding for the website.
The meta viewport tag

    sets the viewport width and scale for the website to adjust for different devices.

<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />

    Good to know: You can overwrite the default viewport meta tag.

Ordering

Metadata is evaluated in order, starting from the root segment down to the segment closest to the final page.js segment. For example:

    app/layout.tsx (Root Layout)
    app/blog/layout.tsx (Nested Blog Layout)
    app/blog/[slug]/page.tsx (Blog Page)

Merging

Following the evaluation order, Metadata objects exported from multiple segments in the same route are shallowly merged together to form the final metadata output of a route. Duplicate keys are replaced based on their ordering.

This means metadata with nested fields such as openGraph and robots that are defined in an earlier segment are overwritten by the last segment to define them.
Overwriting fields

export const metadata = {
  title: 'Acme',
  openGraph: {
    title: 'Acme',
    description: 'Acme is a...',
  },
}

export const metadata = {
  title: 'Blog',
  openGraph: {
    title: 'Blog',
  },
}
 
// Output:
// <title>Blog</title>
// <meta property="og:title" content="Blog" />

In the example above:

    title from app/layout.js is replaced by title in app/blog/page.js.
    All openGraph fields from app/layout.js are replaced in app/blog/page.js because app/blog/page.js sets openGraph metadata. Note the absence of openGraph.description.

If you'd like to share some nested fields between segments while overwriting others, you can pull them out into a separate variable:

export const openGraphImage = { images: ['http://...'] }

import { openGraphImage } from './shared-metadata'
 
export const metadata = {
  openGraph: {
    ...openGraphImage,
    title: 'Home',
  },
}

import { openGraphImage } from '../shared-metadata'
 
export const metadata = {
  openGraph: {
    ...openGraphImage,
    title: 'About',
  },
}

In the example above, the OG image is shared between app/layout.js and app/about/page.js while the titles are different.
Inheriting fields

export const metadata = {
  title: 'Acme',
  openGraph: {
    title: 'Acme',
    description: 'Acme is a...',
  },
}

export const metadata = {
  title: 'About',
}
 
// Output:
// <title>About</title>
// <meta property="og:title" content="Acme" />
// <meta property="og:description" content="Acme is a..." />

Notes

    title from app/layout.js is replaced by title in app/about/page.js.
    All openGraph fields from app/layout.js are inherited in app/about/page.js because app/about/page.js doesn't set openGraph metadata.

Dynamic Image Generation

The ImageResponse constructor allows you to generate dynamic images using JSX and CSS. This is useful for creating social media images such as Open Graph images, Twitter cards, and more.

To use it, you can import ImageResponse from next/og:

import { ImageResponse } from 'next/og'
 
export async function GET() {
  return new ImageResponse(
    (
      <div
        style={{
          fontSize: 128,
          background: 'white',
          width: '100%',
          height: '100%',
          display: 'flex',
          textAlign: 'center',
          alignItems: 'center',
          justifyContent: 'center',
        }}
      >
        Hello world!
      </div>
    ),
    {
      width: 1200,
      height: 600,
    }
  )
}

ImageResponse integrates well with other Next.js APIs, including Route Handlers and file-based Metadata. For example, you can use ImageResponse in a opengraph-image.tsx file to generate Open Graph images at build time or dynamically at request time.

ImageResponse supports common CSS properties including flexbox and absolute positioning, custom fonts, text wrapping, centering, and nested images. See the full list of supported CSS properties.

    Good to know:

        Examples are available in the Vercel OG Playground

.
ImageResponse uses @vercel/og
, Satori

        , and Resvg to convert HTML and CSS into PNG.
        Only the Edge Runtime is supported. The default Node.js runtime will not work.
        Only flexbox and a subset of CSS properties are supported. Advanced layouts (e.g. display: grid) will not work.
        Maximum bundle size of 500KB. The bundle size includes your JSX, CSS, fonts, images, and any other assets. If you exceed the limit, consider reducing the size of any assets or fetching at runtime.
        Only ttf, otf, and woff font formats are supported. To maximize the font parsing speed, ttf or otf are preferred over woff.

JSON-LD

JSON-LD

is a format for structured data that can be used by search engines to understand your content. For example, you can use it to describe a person, an event, an organization, a movie, a book, a recipe, and many other types of entities.

Our current recommendation for JSON-LD is to render structured data as a <script> tag in your layout.js or page.js components. For example:

export default async function Page({ params }) {
  const product = await getProduct(params.id)
 
  const jsonLd = {
    '@context': 'https://schema.org',
    '@type': 'Product',
    name: product.name,
    image: product.image,
    description: product.description,
  }
 
  return (
    <section>
      {/* Add JSON-LD to your page */}
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
      />
      {/* ... */}
    </section>
  )
}

You can validate and test your structured data with the Rich Results Test
for Google or the generic Schema Markup Validator

.

You can type your JSON-LD with TypeScript using community packages like schema-dts

:

import { Product, WithContext } from 'schema-dts'
 
const jsonLd: WithContext<Product> = {
  '@context': 'https://schema.org',
  '@type': 'Product',
  name: 'Next.js Sticker',
  image: 'https://nextjs.org/imgs/sticker.png',
  description: 'Dynamic at the speed of static.',
}