SEO in Next.js v13 App directory

Fatemeh Samie
4 min readJun 2, 2023

--

recently, Next team stabilized the app directory. in the new version of Next, the approach of applying meta tags with the purpose of good SEO and optimizing for Google bots has changed.
In the previous version, we used the ``Head.tsx`` component to add our meta tags but now it’s completely different and the flow is much easier and more effective.
there are two ways for us to add meta tags to our pages that are only supported in Server Components.

Static way:
the metadata should be an object with Specified keys the same as meta tags properties and names.

if you don’t know typescript, and you don’t use it in your project, you don’t need to import Metadata from next to use it as a type for metadata object.

metadata object could be used in ``layout.tsx`` or ``page.tsx``

import { Metadata } from 'next';

export const metadata: Metadata = {
title: '...',
description: '...',
};

export default function Page() {}
export const metadata = {
generator: 'Next.js',
applicationName: 'Next.js',
referrer: 'origin-when-cross-origin',
keywords: ['Next.js', 'React', 'JavaScript'],
authors: [{ name: 'Seb' }, { name: 'Josh', url: 'https://nextjs.org' }],
colorScheme: 'dark',
creator: 'Jiachi Liu',
publisher: 'Sebastian Markbåge',
formatDetection: {
email: false,
address: false,
telephone: false,
},
};

you can also create open graphs for your page like the below:

import { Metadata } from 'next';

export const metadata: Metadata = {
title: product.title,
openGraph: {
images: ['/some-specific-page-image.jpg', ...previousImages],
},
};

you can use Dynamic Image Generation 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.

attention: don't forget about exporting metadata object or generateMetaData function.

Dynamic way:
Next 13 has its own way of dynamically creating meta tags. ``generateMetadata`` function returns an object which contains our meta tags data which has been fetched from your API.

import { 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) {}

note: according to the next documentation When rendering a route, Next.js will automatically deduplicate fetch requests for the same data across generateMetadata, generateStaticParams, Layouts, Pages, and Server Components. React cache can be used if fetch is unavailable.

any file base metadata is available too, such as sitemaps which have static and dynamic ways, robots, opengraph-image or twitter-image, and favicon,… and you should consider that File-based metadata has the higher priority and will override any config-based metadata.

if your layout or upper layer has its own metadata and your page or layout child has its own metadata, metadata will be overwritten, 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, and if the layout has a metadata property that its child doesn't, the child will inherit that metadata.


//in layout.tsx
export const metadata = {
title: 'layout title',
openGraph: {
title: 'lay',
description: 'lay is a...',
},
};

//in page.tsx
export const metadata = {
title: 'About page',
};

//the output for the page.tsx will be :
// <title>About page</title>
// <meta property="og:title" content="lay" />
// <meta property="og:description" content="lay is a..." />

JSON-LD in the app directory

JSON-LD is a lightweight Linked Data format. It is easy for humans to read and write. It is based on the already successful JSON format and provides a way to help JSON data interoperate at Web-scale. In simpler terms, JSON-LD is a way of adding structured data to a web page by including specific attributes in the HTML markup. This allows search engines to better understand the content of the page and present it more effectively in search results.

export default async function Page({ params }) {


//clean code for more than one jason-ld structure
const generateJsonLd =()=>{
//fetched from api or static like bottom
const jsonLd =[ {
'@context': 'https://schema.org',
'@type': 'Product',
name: product.name,
image: product.image,
description: product.description,
},{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "What is....?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Yes"
}
},
{
"@type": "Question",
"name": "is this ...?",
"acceptedAnswer": {
"@type": "Answer",
"text": "some answer"
}
}]

return jasonLd.map((schema,index)=>
<script
key={index}
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(schema),
}}
/>)
}



return (
<section>
{/* Add JSON-LD to your page */}
{generateJsonLd()}
{/* ... */}
</section>
);
}

You can also type your JSON-LD with TypeScript using community packages like schema-dts.
there is so much amazing stuff you can do with JSON-LD and opengraphs to optimize your application for SEO and search engines.

I hope you enjoyed this article. Thank you for reading it.

--

--