Next.js Examples

App Router patterns and server/client component examples

Server Component

Render countries data at build time for optimal performance.

app/countries/page.tsx
import { allCountries } from 'countries-cities-ar';

export default function CountriesPage() {
  // Data is loaded at build time - no client JS needed
  const arabCountries = allCountries.filter(c => 
    ['EG', 'SA', 'AE', 'KW', 'QA', 'BH', 'OM', 'YE', 'SY', 'LB', 'JO', 'IQ'].includes(c.code)
  );
  
  return (
    <div className="grid grid-cols-3 gap-4">
      {arabCountries.map(country => (
        <div key={country.code} className="p-4 border rounded-lg">
          <h2 className="font-bold text-lg">{country.nameAr}</h2>
          <p className="text-gray-600">{country.name}</p>
          <p className="text-sm mt-2">
            {country.cities.length} محافظة
          </p>
        </div>
      ))}
    </div>
  );
}

Dynamic Routes

Create dynamic pages for each country.

app/country/[code]/page.tsx
import { getCountryByCode, allCountries } from 'countries-cities-ar';
import { notFound } from 'next/navigation';

export async function generateStaticParams() {
  return allCountries.map((country) => ({
    code: country.code,
  }));
}

export default function CountryPage({ params }: { params: { code: string } }) {
  const country = getCountryByCode(params.code.toUpperCase());
  
  if (!country) {
    notFound();
  }
  
  return (
    <div>
      <h1 className="text-3xl font-bold mb-4">
        {country.nameAr} - {country.name}
      </h1>
      
      <div className="grid grid-cols-4 gap-3">
        {country.cities.map((city, idx) => (
          <div key={idx} className="p-3 bg-gray-800 rounded">
            <div className="font-medium">{city.nameAr || city.name}</div>
            <div className="text-sm text-gray-400">{city.name}</div>
          </div>
        ))}
      </div>
    </div>
  );
}

Client Component with Server Action

Interactive search using server actions.

app/search/page.tsx
import { searchCountries } from 'countries-cities-ar';
import SearchForm from './search-form';

async function searchAction(query: string) {
  'use server';
  
  if (!query || query.length < 2) {
    return [];
  }
  
  const results = searchCountries(query, 'ar');
  return results.slice(0, 10).map(country => ({
    code: country.code,
    name: country.name,
    nameAr: country.nameAr,
    cities: country.cities.length,
  }));
}

export default function SearchPage() {
  return (
    <div>
      <h1 className="text-2xl font-bold mb-6">Search Countries</h1>
      <SearchForm searchAction={searchAction} />
    </div>
  );
}
app/search/search-form.tsx
'use client';

import { useState, useTransition } from 'react';

export default function SearchForm({ searchAction }) {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();
  
  const handleSearch = async (e) => {
    e.preventDefault();
    startTransition(async () => {
      const data = await searchAction(query);
      setResults(data);
    });
  };
  
  return (
    <div>
      <form onSubmit={handleSearch} className="mb-6">
        <input
          type="text"
          value={query}
          onChange={(e) => setQuery(e.target.value)}
          placeholder="ابحث عن دولة..."
          className="px-4 py-2 bg-gray-900 border border-gray-700 rounded text-white"
        />
        <button 
          type="submit"
          disabled={isPending}
          className="ml-2 px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded"
        >
          {isPending ? 'جاري البحث...' : 'بحث'}
        </button>
      </form>
      
      <div className="grid grid-cols-2 gap-4">
        {results.map(country => (
          <div key={country.code} className="p-4 border border-gray-700 rounded">
            <h3 className="font-bold text-white">{country.nameAr}</h3>
            <p className="text-gray-400">{country.name}</p>
            <p className="text-sm text-gray-500">{country.cities} cities</p>
          </div>
        ))}
      </div>
    </div>
  );
}

Streaming with Suspense

Load data progressively with React Suspense.

app/dashboard/page.tsx
import { Suspense } from 'react';
import { allCountries } from 'countries-cities-ar';

async function CountryStats() {
  // Simulate delay
  await new Promise(resolve => setTimeout(resolve, 1000));
  
  const stats = {
    total: allCountries.length,
    withArabic: allCountries.filter(c => c.nameAr).length,
    totalCities: allCountries.reduce((acc, c) => acc + c.cities.length, 0),
  };
  
  return (
    <div className="grid grid-cols-3 gap-4">
      <div className="p-4 bg-blue-500/10 border border-blue-500/30 rounded">
        <div className="text-2xl font-bold text-blue-400">{stats.total}</div>
        <div className="text-gray-400">Total Countries</div>
      </div>
      <div className="p-4 bg-green-500/10 border border-green-500/30 rounded">
        <div className="text-2xl font-bold text-green-400">{stats.withArabic}</div>
        <div className="text-gray-400">With Arabic</div>
      </div>
      <div className="p-4 bg-purple-500/10 border border-purple-500/30 rounded">
        <div className="text-2xl font-bold text-purple-400">{stats.totalCities}</div>
        <div className="text-gray-400">Total Cities</div>
      </div>
    </div>
  );
}

export default function DashboardPage() {
  return (
    <div>
      <h1 className="text-2xl font-bold mb-6">Dashboard</h1>
      
      <Suspense fallback={
        <div className="animate-pulse">
          <div className="h-20 bg-gray-800 rounded mb-4"></div>
        </div>
      }>
        <CountryStats />
      </Suspense>
    </div>
  );
}

Countries Cities AR

Documentation

Version
v3.0.1
250 دولة • 4,642 محافظة