DEV Community

Shashwat Maurya
Shashwat Maurya

Posted on

How I Built a Local SEO Optimised Next.js Website That Ranked on Google in 45 Days

I run a small web development agency called Best Web Devlopment agency in varanasi
based in Varanasi, India. This is a technical breakdown of how I built
and optimised a Next.js website that started ranking on Google within
45 days — covering the exact implementation decisions that made the
difference.

The Stack

Framework:     Next.js 14 (App Router)
Hosting:       Vercel
CMS:           Custom MongoDB + Node.js API
Styling:       Tailwind CSS
Analytics:     Google Analytics 4
SEO:           next-sitemap + custom schema
Enter fullscreen mode Exit fullscreen mode

1. App Router Metadata Implementation

The biggest SEO win in Next.js 14 is the new Metadata API.
Static metadata on every page is no longer optional.

// app/layout.js
export const metadata = {
  metadataBase: new URL('https://synor.in'),
  title: {
    default: 'Synor — Web Development Agency Varanasi',
    template: '%s | Synor'
  },
  description: 'Professional web development and digital marketing agency in Varanasi.',
  openGraph: {
    type: 'website',
    locale: 'en_IN',
    url: 'https://synor.in',
    siteName: 'Synor',
  },
  robots: {
    index: true,
    follow: true,
    googleBot: {
      index: true,
      follow: true,
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

For dynamic blog pages I generate metadata per post:

// app/blog/[slug]/page.js
export async function generateMetadata({ params }) {
  const post = await getPost(params.slug)
  return {
    title: post.seoTitle,
    description: post.seoDescription,
    keywords: post.seoKeywords,
    openGraph: {
      title: post.seoTitle,
      description: post.seoDescription,
      images: [{ url: post.featuredImage }],
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

2. LocalBusiness Schema Markup

This was the single biggest ranking factor for local keywords.
I inject schema directly in the root layout:

// components/LocalBusinessSchema.js
export default function LocalBusinessSchema() {
  const schema = {
    "@context": "https://schema.org",
    "@type": "LocalBusiness",
    "name": "Synor",
    "description": "Web development and digital marketing agency in Varanasi",
    "url": "https://synor.in",
    "telephone": "+919125258698",
    "address": {
      "@type": "PostalAddress",
      "streetAddress": "Awas Vikas Colony, Pandeypur",
      "addressLocality": "Varanasi",
      "addressRegion": "Uttar Pradesh",
      "postalCode": "221002",
      "addressCountry": "IN"
    },
    "geo": {
      "@type": "GeoCoordinates",
      "latitude": "25.3176",
      "longitude": "82.9739"
    },
    "openingHours": "Mo-Sa 09:00-18:00",
    "priceRange": "₹₹",
    "areaServed": {
      "@type": "City",
      "name": "Varanasi"
    },
    "sameAs": [
      "https://www.linkedin.com/company/synor",
      "https://github.com/synor"
    ]
  }

  return (

  )
}
Enter fullscreen mode Exit fullscreen mode

3. ISR for Blog Pages

Static generation for every blog post with 1 hour revalidation.
This gives you static page speed with dynamic content freshness:

// app/blog/[slug]/page.js
export const revalidate = 3600 // 1 hour

export async function generateStaticParams() {
  const posts = await getAllPostSlugs()
  return posts.map(post => ({
    slug: post.slug
  }))
}
Enter fullscreen mode Exit fullscreen mode

Result — blog pages consistently score 95+ on PageSpeed Insights
because they serve as static HTML with no server render delay.

4. Dynamic Sitemap Generation

// app/sitemap.js
export default async function sitemap() {
  const blogs = await getAllBlogs()
  const caseStudies = await getAllCaseStudies()

  const blogUrls = blogs.map(blog => ({
    url: `https://synor.in/blog/${blog.slug}`,
    lastModified: blog.updatedAt,
    changeFrequency: 'weekly',
    priority: 0.8,
  }))

  const caseStudyUrls = caseStudies.map(cs => ({
    url: `https://synor.in/case-studies/${cs.slug}`,
    lastModified: cs.updatedAt,
    changeFrequency: 'monthly',
    priority: 0.9,
  }))

  return [
    {
      url: 'https://synor.in',
      lastModified: new Date(),
      changeFrequency: 'weekly',
      priority: 1,
    },
    {
      url: 'https://synor.in/services',
      lastModified: new Date(),
      changeFrequency: 'monthly',
      priority: 0.9,
    },
    ...blogUrls,
    ...caseStudyUrls,
  ]
}
Enter fullscreen mode Exit fullscreen mode

This auto-updates every time a new blog or case study is published
from the CMS — no manual sitemap management needed.

5. FAQ Schema on Blog Posts

Every blog has an FAQ section. Adding FAQ schema gets you
featured snippets on Google — the answer box above all results:

// components/FAQSchema.js
export default function FAQSchema({ faqs }) {
  const schema = {
    "@context": "https://schema.org",
    "@type": "FAQPage",
    "mainEntity": faqs.map(faq => ({
      "@type": "Question",
      "name": faq.question,
      "acceptedAnswer": {
        "@type": "Answer",
        "text": faq.answer
      }
    }))
  }

  return (

  )
}
Enter fullscreen mode Exit fullscreen mode

6. Image Optimization

Next.js Image component with WebP conversion and lazy loading
on every image across the site:

import Image from 'next/image'

// Every image on the site uses this pattern

Enter fullscreen mode Exit fullscreen mode

The alt text pattern ${post.title} — Synor Web Development
naturally includes target keywords on every image without
keyword stuffing.

7. Core Web Vitals Fixes That Moved the Needle

Three specific fixes that improved PageSpeed from 67 to 91 on mobile:

Fix 1 — Font preloading

// app/layout.js

Enter fullscreen mode Exit fullscreen mode

Fix 2 — Above fold image priority

// Hero image gets priority={true} — loads before anything else

Enter fullscreen mode Exit fullscreen mode

Fix 3 — Suspense boundaries for dynamic content

}>


Enter fullscreen mode Exit fullscreen mode

This prevents the entire page from blocking on data fetching —
users see the layout instantly while content loads.

Results After 45 Days

  • Page moved from position 90+ to position 85 on target keyword
  • 23 out of 40 pages indexed within first 30 days
  • Mobile PageSpeed consistently 88 to 92
  • Core Web Vitals all green in Search Console
  • First organic traffic from Google within 3 weeks of launch

Still climbing — will post a follow-up at the 90 day mark.

What I Would Do Differently

  1. Add schema markup from day 1 — I added it in week 2 which delayed indexing by about a week
  2. Submit sitemap manually on launch day — do not wait for Google to discover it
  3. Request indexing manually for every page in Search Console immediately after publishing — this alone cut indexing time from weeks to days

If you are building a local business website in Next.js and want
to rank on Google, the technical fundamentals above are the
non-negotiables. Content and backlinks matter — but broken
technical SEO kills everything else you do.

Happy to answer questions about any specific implementation
in the comments.

— Shashwat, Synor


markdown

Enter fullscreen mode Exit fullscreen mode

Top comments (0)