Setting up SEO for a website on Next
SEO for Next.js on RelaxDev - complete guide
Everything you need to do to get your site into search engines. Specifics of Next.js App Router: robots and sitemap are specified by TypeScript files in the app/ folder, confirmation files are in public/.
1. robots.txt
Create src/app/robots.ts - Next.js itself will give it to the address /robots.txt:
// src/app/robots.ts
import type { MetadataRoute } from 'next';
export default function robots(): MetadataRoute.Robots {
return {
rules: [
{
userAgent: '*',
allow: '/',
disallow: ['/api/', '/dashboard/', '/profile/', '/admin/', '/login/', '/auth/'],
},
],
sitemap: 'https://ваш-домен.ru/sitemap.xml',
};
}
Check: open https://ваш-домен.ru/robots.txt - a text with a list of rules should open.
2. sitemap.xml
Create src/app/sitemap.ts:
// src/app/sitemap.ts
import type { MetadataRoute } from 'next';
export const dynamic = 'force-dynamic';
export default function sitemap(): MetadataRoute.Sitemap {
return [
{ url: 'https://ваш-домен.ru', lastModified: new Date(), changeFrequency: 'weekly', priority: 1 },
{ url: 'https://ваш-домен.ru/about', lastModified: new Date(), changeFrequency: 'monthly', priority: 0.8 },
];
}
With dynamic content from the database (Prisma):
import type { MetadataRoute } from 'next';
import { prisma } from '@/lib/prisma';
export const dynamic = 'force-dynamic';
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const baseUrl = 'https://ваш-домен.ru';
const posts = await prisma.post.findMany({
where: { published: true },
select: { slug: true, updatedAt: true },
});
return [
{ url: baseUrl, lastModified: new Date(), changeFrequency: 'weekly', priority: 1 },
{ url: `${baseUrl}/about`, changeFrequency: 'monthly', priority: 0.8 },
...posts.map((p) => ({
url: `${baseUrl}/blog/${p.slug}`,
lastModified: p.updatedAt,
changeFrequency: 'weekly' as const,
priority: 0.7,
})),
];
}
force-dynamic is needed so that the sitemap is not built during the build: then Next.js will not go to the database at the build stage.
Check: open https://ваш-домен.ru/sitemap.xml - XML with a list of pages should open.
3. Block service pages from indexing
Pages with authorization and personal data should not be allowed into search engines. In App Router, an entire section is closed with one line in layout.tsx - all subpages will receive noindex:
// src/app/(dashboard)/layout.tsx
import type { Metadata } from 'next';
export const metadata: Metadata = {
robots: { index: false, follow: false },
};
This covers /dashboard, /profile, /settings and everything inside.
For a separate page - the same in its page.tsx.
Check that the page is closed: in the browser Ctrl+U → find <meta name="robots" content="noindex">. Or Google Search Console → “URL Check”.
On RelaxDev, the technical address of the project is проект.relaxdev.ru and preview addresses are already given with noindex - they do not create duplicates of your site in the search results.
4. Site name in search results
Google displays the site name above the result title. If the site does not report it, Google substitutes the domain - and the Cyrillic domain in the form xn--…. The name is set by the WebSite markup on the main page and og:site_name:
// src/app/layout.tsx
export const metadata: Metadata = {
metadataBase: new URL('https://ваш-домен.ru'),
openGraph: { siteName: 'Название сайта' },
};
// src/app/page.tsx — внутри разметки главной
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'WebSite',
name: 'Название сайта',
alternateName: 'ваш-домен.ru',
url: 'https://ваш-домен.ru/',
}),
}}
/>
url must match the main address in canonical. Google will pick up the name the next time it crawls the homepage.
5. Site confirmation
The easiest way in Next.js is meta tags via metadata, without files:
// src/app/layout.tsx
export const metadata: Metadata = {
verification: {
google: 'код-из-search-console',
yandex: 'код-из-вебмастера',
},
};
If it is more convenient to use files, in Google Search Console and Yandex Webmaster, select the “HTML file” method, download the files and put them in public/:
public/
google123abc456def.html ← Google
yandex_12345abcdef.html ← Яндекс
Files from public/ are available at the root address automatically: after deployment, https://ваш-домен.ru/google123abc456def.html will open immediately. Commit, deploy and click “Confirm” in the panel of each search engine.
6. Yandex Metrica
The counter is not only analytics: according to experience, with it Yandex finds and crawls new pages faster.
// src/app/layout.tsx
import Script from 'next/script';
// Внутри <body>:
<Script id="yandex-metrika" strategy="lazyOnload">
{`(function(m,e,t,r,i,k,a){
m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
m[i].l=1*new Date();
k=e.createElement(t),a=e.getElementsByTagName(t)[0];
k.async=1;k.src=r;a.parentNode.insertBefore(k,a)
})(window,document,"script","https://mc.yandex.ru/metrika/tag.js","ym");
ym(XXXXXXXX,"init",{ clickmap:true, trackLinks:true, accurateTrackBounce:true, webvisor:true });`}
</Script>
Where XXXXXXXX is the counter number from metrika.yandex.ru. lazyOnload loads the counter after the page is loaded and does not take away points from PageSpeed. Don't forget to mention Metrica in your cookie policy.
7. Submit sitemap to search engines
Without this, search engines may not find new pages for weeks.
- Google Search Console:Indexing → Sitemaps → add
https://ваш-домен.ru/sitemap.xml→ “Submit”.
-Yandex Webmaster:Indexing → Sitemap files → add https://ваш-домен.ru/sitemap.xml.
8. Cyrillic domain (.рф and others)
The domain кухниостровский.рф is stored in DNS and browsers as xn--b1albebcsjatkdjj4a.xn--p1ai - this is the same address. What is important for SEO:
-One address form everywhere:in metadataBase, canonical, sitemap, robots and internal links. Punycode (xn--…) is understood by all robots without exception.
-Site name in search results- the WebSite markup from section 4 is required. Without it, Google will show xn--… above the title.
-Yandexshows Cyrillic domains normally. Webmaster has a “Site Name Register”: you can set it to look like КухниОстровский.рф.
-The link copied from the search results or browsercomes to punycode - it’s working, it’s the same page.
-In the RelaxDev panelthe domain can be entered in Cyrillic: it is stored in punycode, displayed in Cyrillic, the records from the registrar are the same.
Read more about records, www and redirects in the article “Domains and DNS on RelaxDev”.
Checklist
https://ваш-домен.ru/robots.txtopenshttps://ваш-домен.ru/sitemap.xmlopens with the required pages- Closed sections (
/dashboard/,/admin/) haverobots: { index: false } - On the main page there is a WebSite markup with the name of the site
- The site is confirmed in Google Search Console and Yandex Webmaster
- Metrica counter installed
- Sitemap sent to both panels
- One option is selected for the domain - with or without www - the second is redirected to itTime frame: usually Google - from several days to a week, Yandex - several days.
Read also
- A year on Vercel and one evening of moving: bazariara.ge has become faster and is opening again in Russia - 100 out of 100 in PageSpeed after the move
- The same story for SEO specialists - on vc.ru
- Environment preview: how to check a branch without breaking production
Questions - write in the comments 👇
This is a translation of the original Russian article. Comments are on the Russian page.