Sitemap & SEO

Search Engine Optimization (SEO) and a well-structured sitemap are crucial for the discoverability and visibility of the Kenya Estates platform. Next.js provides excellent features to build SEO-friendly applications.

Sitemap Generation

A sitemap is an XML file that lists all the important URLs of a website, helping search engines crawl and index the site more effectively. In Next.js (using the App Router), sitemaps can be generated dynamically or statically.

The Kenya Estates platform uses a sitemap.ts file in the app directory to generate the main sitemap. Additionally, specific sections of the site, like properties and blog articles, might have their own sitemap generation logic if they are numerous.


// Example: app/sitemap.ts
import { MetadataRoute } from 'next';
import { getAllProperties, getAllArticles, getAllAgents } from '@/lib/data'; // Hypothetical data fetching functions

const URL = 'https://www.kenyaestates.com'; // Replace with actual domain

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const staticPages = [
    '', // Homepage
    '/properties',
    '/agents',
    '/blog',
    '/market-insights',
    '/compare',
    '/terms',
    '/privacy',
    '/cookies',
    // ... other static pages
  ].map((route) => ({
    url: `${URL}${route}`,
    lastModified: new Date().toISOString(),
    changeFrequency: 'monthly' as 'monthly', // Type assertion
    priority: route === '' ? 1 : 0.8,
  }));

  const properties = await getAllProperties(); // Fetch all property IDs/slugs
  const propertyPages = properties.map((property) => ({
    url: `${URL}/properties/${property.id}`,
    lastModified: property.updatedAt || new Date().toISOString(),
    changeFrequency: 'weekly' as 'weekly',
    priority: 0.7,
  }));

  const articles = await getAllArticles(); // Fetch all article slugs
  const articlePages = articles.map((article) => ({
    url: `${URL}/blog/${article.slug}`,
    lastModified: article.updatedAt || new Date().toISOString(),
    changeFrequency: 'weekly' as 'weekly',
    priority: 0.6,
  }));
  
  // Similar logic for agents, etc.

  return [
    ...staticPages,
    ...propertyPages,
    ...articlePages,
    // ... other dynamic pages
  ];
}

This file typically exports an asynchronous function that returns an array of sitemap entries. It fetches dynamic data (e.g., property IDs, article slugs) to include all relevant pages.

The platform also has sitemap files for specific routes like app/properties/sitemap.ts, app/agents/sitemap.ts, and app/blog/sitemap.ts. These generate sitemaps specific to those sections, which can be linked from a sitemap index file if the total number of URLs is very large.

Robots.txt

The robots.txt file instructs search engine crawlers on which pages or sections of the site should not be crawled or indexed. Next.js allows you to add a static robots.txt file to the public directory, or generate it dynamically via a robots.ts file in the app directory.

The Kenya Estates platform uses app/robots.ts:


// Example: app/robots.ts
import { MetadataRoute } from 'next';

export default function robots(): MetadataRoute.Robots {
  return {
    rules: [
      {
        userAgent: '*',
        allow: '/',
        disallow: ['/dashboard/', '/api/'], // Example: disallow crawling of dashboard and API routes
      },
    ],
    sitemap: 'https://www.kenyaestates.com/sitemap.xml', // Replace with actual domain
  };
}

Metadata API

Next.js provides a powerful Metadata API to manage HTML <head>elements like <title>, <meta name="description">, Open Graph tags, and more. This is essential for SEO.

You can define static metadata by exporting a metadata object from a layout or page file:


// Example: app/properties/page.tsx
import { Metadata } from 'next';

export const metadata: Metadata = {
  title: 'Properties for Sale and Rent | Kenya Estates',
  description: 'Find the latest properties for sale and rent in Kenya.',
};

export default function PropertiesPage() {
  // ... page content
}

For dynamic metadata (e.g., for a specific property page), you can export an asynchronous function called generateMetadata:


// Example: app/properties/[id]/page.tsx
import { Metadata, ResolvingMetadata } from 'next';
import { getPropertyById } from '@/lib/data'; // Hypothetical data fetching function

type Props = {
  params: { id: string };
};

export async function generateMetadata(
  { params }: Props,
  parent: ResolvingMetadata
): Promise<Metadata> {
  const id = params.id;
  const property = await getPropertyById(id);

  if (!property) {
    return {
      title: 'Property Not Found | Kenya Estates',
    };
  }

  return {
    title: `${property.title} | Kenya Estates`,
    description: property.description.substring(0, 160), // Use a snippet of the description
    openGraph: {
      title: property.title,
      description: property.description.substring(0, 160),
      images: [{ url: property.mainImageUrl }],
    },
  };
}

export default async function PropertyDetailsPage({ params }: Props) {
  // ... page content
}

This ensures each page has unique and relevant metadata, improving its ranking and appearance in search results.

Structured Data

Implementing structured data (e.g., using Schema.org vocabulary) can help search engines understand the content of your pages better and can enable rich snippets in search results. For a real estate platform, schemas likeRealEstateListing, Place, and Organization are relevant.

Structured data can be added as a JSON-LD script tag within the <head>of a page, often managed via the Metadata API.


// In generateMetadata or as part of the metadata object
const property = /* ... fetched property data ... */;
const structuredData = {
  '@context': 'https://schema.org',
  '@type': 'RealEstateListing', // Or 'Product' for a more general approach
  name: property.title,
  description: property.description,
  image: property.mainImageUrl,
  url: `https://www.kenyaestates.com/properties/${property.id}`,
  offers: {
    '@type': 'Offer',
    price: property.price,
    priceCurrency: 'KES', // Or relevant currency
    availability: 'https://schema.org/InStock', // Or other appropriate availability
  },
  // ... other relevant properties like address, geoCoordinates, etc.
};

// In the return of generateMetadata:
return {
  // ... other metadata
  other: {
    structuredData: JSON.stringify(structuredData), // Custom key, handle in layout
  },
  // Or directly in the component if not using metadata API for this:
  // <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }} />
};

// If using 'other' in metadata, you'd need to render it in your root layout:
// app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
  const pageMetadata = // ... logic to get current page's metadata if needed, or it's part of Next.js context
  const structuredDataContent = pageMetadata?.other?.structuredData as string | undefined;

  return (
    <html>
      <head>
        {structuredDataContent && (
          <script
            type="application/ld+json"
            dangerouslySetInnerHTML={{ __html: structuredDataContent }}
          />
        )}
      </head>
      <body>{children}</body>
    </html>
  );
}

Note: Next.js 13.2+ has built-in support for JSON-LD scripts via the metadata.script option, which is a cleaner way to handle this.

Performance

Site speed is a significant SEO factor. Next.js's optimizations like automatic code splitting, image optimization (next/image), static generation, and server components contribute positively to performance. Ensuring efficient data fetching and optimized client-side JavaScript is also crucial.