<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Janak Pokharel</title>
    <description>The latest articles on DEV Community by Janak Pokharel (@janak_pokharel_485508294d).</description>
    <link>https://dev.to/janak_pokharel_485508294d</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4188113%2Fbd8ba06d-e662-47c8-9c06-0fa21dec1905.png</url>
      <title>DEV Community: Janak Pokharel</title>
      <link>https://dev.to/janak_pokharel_485508294d</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/janak_pokharel_485508294d"/>
    <language>en</language>
    <item>
      <title>Next.js SEO checklist: what actually gets App Router pages indexed</title>
      <dc:creator>Janak Pokharel</dc:creator>
      <pubDate>Fri, 16 Oct 2026 09:13:58 +0000</pubDate>
      <link>https://dev.to/janak_pokharel_485508294d/nextjs-seo-checklist-what-actually-gets-app-router-pages-indexed-5fha</link>
      <guid>https://dev.to/janak_pokharel_485508294d/nextjs-seo-checklist-what-actually-gets-app-router-pages-indexed-5fha</guid>
      <description>&lt;p&gt;&lt;strong&gt;Last Reviewed:&lt;/strong&gt; October 16, 2026 | &lt;strong&gt;Fact-Checked:&lt;/strong&gt; October 16, 2026&lt;/p&gt;

&lt;p&gt;To get Next.js App Router pages indexed, let Google crawl them, give each page a unique title and canonical through the Metadata API, render the content as text on the server, list real URLs in sitemap.ts, and check results with Search Console's URL Inspection tool. Google says its AI features need no special markup. Skip sitemap priority, the keywords tag and FAQ schema for rich results.&lt;/p&gt;

&lt;p&gt;Next.js SEO is mostly about not breaking basic things. A page that Google cannot crawl, render or reach through links will not be indexed, however good its metadata looks.&lt;/p&gt;

&lt;p&gt;I'm Janak Pokharel, an SEO and paid ads consultant. I'm not a full-time developer, but my own site, janakpokharel.com.np, runs on Next.js with Supabase on Vercel, and I audit it with Google Search Console on a schedule. This checklist covers what Google needs to index a page, how to set metadata and canonicals, how to add JSON-LD safely, what belongs in sitemap.ts and robots.ts, and how to check indexing after a deploy. I checked every code pattern against the current Next.js docs on October 16, 2026. Some older guides still read &lt;code&gt;params&lt;/code&gt; synchronously, so I note what the docs say now. I also list the mistakes I found on my own site. Use the checklist before launch and again after every deploy. The goal is simple: pages that Google can reach, read and trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does Google need from a Next.js page to index it?
&lt;/h2&gt;

&lt;p&gt;Google needs four things to index a Next.js page: permission to crawl it in robots.txt, a link path from pages it already knows, content it can read as text after rendering, and a canonical URL that points to the version you want indexed. Google also states that indexing is never guaranteed, even when every requirement is met.&lt;/p&gt;

&lt;p&gt;Those four points come from two Google sources. &lt;a href="https://developers.google.com/search/docs/appearance/ai-features" rel="noopener noreferrer"&gt;Google's AI features documentation&lt;/a&gt; tells site owners to allow crawling, make content easy to find through internal links and keep important content in text form. The &lt;a href="https://developers.google.com/search/docs/fundamentals/seo-starter-guide" rel="noopener noreferrer"&gt;SEO Starter Guide&lt;/a&gt; says Google primarily finds pages through links from pages it already crawled.&lt;/p&gt;

&lt;p&gt;Next.js helps with the third point. Server Components render HTML on the server. If a page can be prerendered, its metadata lands in the initial HTML. If metadata streams in, the &lt;a href="https://nextjs.org/docs/app/api-reference/functions/generate-metadata" rel="noopener noreferrer"&gt;Next.js docs&lt;/a&gt; say it is appended to the body and that Googlebot interprets it correctly. Bots that cannot run JavaScript still get metadata in the head.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you set metadata and canonicals in the App Router?
&lt;/h2&gt;

&lt;p&gt;Set &lt;code&gt;metadataBase&lt;/code&gt; once in the root layout, then export a &lt;code&gt;metadata&lt;/code&gt; object or a &lt;code&gt;generateMetadata&lt;/code&gt; function from each page or layout. Both work only in Server Components. In current Next.js, &lt;code&gt;params&lt;/code&gt; is a Promise, so await it. Use &lt;code&gt;alternates.canonical&lt;/code&gt; to name the preferred URL so duplicates resolve to one indexable page.&lt;/p&gt;

&lt;p&gt;Root layout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/layout.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Metadata&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;metadataBase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A dynamic page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/blog/[slug]/page.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Metadata&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateMetadata&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Metadata&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// your own data source&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;seoTitle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;seoDescription&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;alternates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;canonical&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/blog/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three rules from the docs are worth remembering. You cannot export both &lt;code&gt;metadata&lt;/code&gt; and &lt;code&gt;generateMetadata&lt;/code&gt; from the same segment. If a page needs client features, keep &lt;code&gt;page.tsx&lt;/code&gt; as a Server Component and move the interactive part into a separate client file. A relative URL in a metadata field without &lt;code&gt;metadataBase&lt;/code&gt; causes a build error. Also give every page its own title, and skip the keywords field. Google Search does not use the keywords meta tag.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you add JSON-LD safely?
&lt;/h2&gt;

&lt;p&gt;Render JSON-LD as a script tag with type &lt;code&gt;application/ld+json&lt;/code&gt; inside a layout or page component. The Next.js documentation warns that &lt;code&gt;JSON.stringify&lt;/code&gt; does not sanitize strings, so replace the less-than character with its Unicode escape before injecting. Make sure the structured data matches the visible page text, as Google advises.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// inside app/blog/[slug]/page.tsx&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jsonLd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@context&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://schema.org&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Article&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;datePublished&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publishedAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;dateModified&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;updatedAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Person&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Your Name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;article&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"application/ld+json"&lt;/span&gt;
      &lt;span class="na"&gt;dangerouslySetInnerHTML&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;__html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jsonLd&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;lt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;u003c&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* page content */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;article&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://nextjs.org/docs/app/guides/json-ld" rel="noopener noreferrer"&gt;Next.js JSON-LD guide&lt;/a&gt; recommends a native script tag, not &lt;code&gt;next/script&lt;/code&gt;, because JSON-LD is data, not executable code. It also points to the Rich Results Test and the Schema Markup Validator for checking your output.&lt;/p&gt;

&lt;h2&gt;
  
  
  What belongs in sitemap.ts and robots.ts?
&lt;/h2&gt;

&lt;p&gt;Your sitemap.ts should list only canonical, indexable URLs and set &lt;code&gt;lastModified&lt;/code&gt; to the real date of the last significant edit. Google ignores &lt;code&gt;priority&lt;/code&gt; and &lt;code&gt;changefreq&lt;/code&gt;, and it uses &lt;code&gt;lastmod&lt;/code&gt; only when the value is consistently and verifiably accurate. Your robots.ts should allow the pages you want indexed and point to the sitemap.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/sitemap.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MetadataRoute&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sitemap&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MetadataRoute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sitemap&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getPublishedPosts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// only published, indexable posts&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lastModified&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;homepageUpdatedAt&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`https://example.com/blog/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;lastModified&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;updatedAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})),&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/robots.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MetadataRoute&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;robots&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;MetadataRoute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Robots&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;userAgent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;disallow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/admin/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;sitemap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com/sitemap.xml&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some examples, including the one in the Next.js sitemap docs, use &lt;code&gt;lastModified: new Date()&lt;/code&gt;. That stamps the current time on every build, so the date says nothing true about the page. Google's &lt;a href="https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap" rel="noopener noreferrer"&gt;sitemap documentation&lt;/a&gt; also limits one sitemap file to 50,000 URLs or 50MB uncompressed.&lt;/p&gt;

&lt;p&gt;What can you safely skip? Three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sitemap &lt;code&gt;priority&lt;/code&gt; and &lt;code&gt;changeFrequency&lt;/code&gt;.&lt;/strong&gt; Google ignores both.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The keywords meta tag.&lt;/strong&gt; Google Search does not use it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FAQPage JSON-LD for rich results.&lt;/strong&gt; Google's &lt;a href="https://developers.google.com/search/docs/appearance/structured-data/faqpage" rel="noopener noreferrer"&gt;FAQ documentation page&lt;/a&gt; states that FAQ rich results stopped appearing in Google Search on May 7, 2026. Mark up genuine Q&amp;amp;A if you like, but do not expect a rich result.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How do you confirm indexing after a deploy?
&lt;/h2&gt;

&lt;p&gt;After each deploy, open Search Console's URL Inspection tool for your most important new or changed URLs. The report shows whether Google has indexed the page and which canonical it chose, using the last indexed version. A live test shows the current page but cannot confirm indexing, and Request Indexing has a daily limit with no guarantee.&lt;/p&gt;

&lt;p&gt;Follow this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Inspect the URL and read the indexed status and the Google-selected canonical.&lt;/li&gt;
&lt;li&gt;If the page is not indexed, run the live test and check the rendered screenshot, which is available only in a live test.&lt;/li&gt;
&lt;li&gt;Compare the canonical Google chose with the one you declared.&lt;/li&gt;
&lt;li&gt;Confirm the URL appears in your sitemap and has at least one internal link pointing to it.&lt;/li&gt;
&lt;li&gt;Wait. Google suggests allowing a few weeks before judging whether a change helped.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can also search for your domain in Google. If results point to your site, you are in the index. The &lt;a href="https://support.google.com/webmasters/answer/9012289" rel="noopener noreferrer"&gt;URL Inspection documentation&lt;/a&gt; explains each field.&lt;/p&gt;

&lt;h2&gt;
  
  
  What went wrong on my own site?
&lt;/h2&gt;

&lt;p&gt;These are real problems from janakpokharel.com.np, found during recurring Search Console and crawl audits. Each one taught me a rule.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Missing environment variables caused silent fallback content.&lt;/strong&gt; Production builds from GitHub ran on Vercel without &lt;code&gt;NEXT_PUBLIC_SUPABASE_URL&lt;/code&gt; and &lt;code&gt;NEXT_PUBLIC_SUPABASE_ANON_KEY&lt;/code&gt;. The site did not crash. It fell back to hardcoded content, so pages looked fine but showed old text. Rule: fail the build when a required variable is missing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/env.ts, imported by your data layer so it runs at build time&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;required&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NEXT_PUBLIC_SUPABASE_URL&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NEXT_PUBLIC_SUPABASE_ANON_KEY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Missing &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;A leftover redirect buried a live post.&lt;/strong&gt; A rule in &lt;code&gt;next.config.mjs&lt;/code&gt; sent one published post to the blog index, while the sitemap still listed it. Rule: compare your redirect sources against your sitemap URLs after every content change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deleted pages kept getting linked.&lt;/strong&gt; After I removed four service pages, a crawl found those URLs still linked 28 times. Twenty-four of the links lived in database content and four were hardcoded in a React page. Rule: content links live in two places, so search both before you delete a page. I repointed each link to the closest live page instead of adding redirects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A wrong upload folder broke a build.&lt;/strong&gt; A GitHub web upload placed changed files in a subfolder instead of the app directories, and the Vercel build failed. The fix was a pull request from a branch, reviewed in a preview deploy before merging to main.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do AI search features need special markup?
&lt;/h2&gt;

&lt;p&gt;No. Google's AI features documentation, last updated December 10, 2025, says a page must be indexed and eligible to show in Search with a snippet. It lists no additional technical requirements, no special schema.org markup and no AI text files. Crawl access still matters, because OpenAI, Anthropic and Google each run separate crawlers with separate robots.txt rules.&lt;/p&gt;

&lt;p&gt;OpenAI documents separate bots for &lt;a href="https://developers.openai.com/api/docs/bots" rel="noopener noreferrer"&gt;ChatGPT search, model training and user-initiated visits&lt;/a&gt;. Anthropic documents &lt;a href="https://support.claude.com/en/articles/8896518-does-anthropic-crawl-data-from-the-web-and-how-can-site-owners-block-the-crawler" rel="noopener noreferrer"&gt;separate bots for training, search and user requests&lt;/a&gt;. Whether to allow the training bots is a business decision, not an SEO one. Check each operator's page before you edit robots.ts, because blocking a search bot can keep your site out of that product's answers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Most Next.js SEO problems are basic: pages Google cannot reach, duplicate URLs, stale sitemaps and silent build fallbacks. Set metadata and canonicals through the Metadata API, escape JSON-LD, keep sitemap dates honest, and inspect new URLs in Search Console after every deploy. Treat the checklist as a routine, not a one-off task, because a single deploy can undo a good setup. Do one thing today: compare your redirect rules with your sitemap. If you want a second pair of eyes on your site's indexing, you can &lt;a href="https://janakpokharel.com.np/contact" rel="noopener noreferrer"&gt;request an SEO proposal&lt;/a&gt;. I also offer &lt;a href="https://janakpokharel.com.np/services/technical-seo" rel="noopener noreferrer"&gt;technical SEO services&lt;/a&gt;, and I explain what an audit costs in &lt;a href="https://janakpokharel.com.np/blog/technical-seo-audit-cost" rel="noopener noreferrer"&gt;my technical SEO audit cost guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Should I use generateMetadata or a metadata object?
&lt;/h3&gt;

&lt;p&gt;Use the static &lt;code&gt;metadata&lt;/code&gt; object when values do not depend on route params or fetched data. Use &lt;code&gt;generateMetadata&lt;/code&gt; when they do. You cannot export both from the same route segment, and both work only in Server Components.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does the App Router hurt SEO because of client rendering?
&lt;/h3&gt;

&lt;p&gt;Not by default. Server Components render HTML on the server, and prerenderable pages include their metadata in the initial HTML. The Next.js docs say streamed metadata is appended to the body and that Googlebot interprets it correctly. Keep important content in server-rendered text.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need a package for sitemaps and robots.txt?
&lt;/h3&gt;

&lt;p&gt;No. Next.js has built-in sitemap.ts and robots.ts conventions. A package can still help with unusual needs, such as very large sites that must be split across many sitemap files, but the basic setup needs no extra dependency.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does FAQ schema still matter?
&lt;/h3&gt;

&lt;p&gt;Google's documentation says FAQ rich results stopped appearing in Google Search on May 7, 2026. You can keep markup for genuine, visible Q&amp;amp;A, but do not add it to win a rich result. Whether other systems use it is a separate question.&lt;/p&gt;

&lt;h3&gt;
  
  
  How long until a new page is indexed?
&lt;/h3&gt;

&lt;p&gt;Google says some changes take hours and others take months, and it suggests waiting a few weeks before judging results. Request Indexing in Search Console has a daily limit and guarantees nothing, so fix crawl and link problems first.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Janak Pokharel is an SEO and paid ads consultant based in Kathmandu and a co-founder of Limi Creatives. He publishes guides at &lt;a href="https://janakpokharel.com.np" rel="noopener noreferrer"&gt;Janak Pokharel's website&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>seo</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
