Free tutorials & notes in Hindi & English · Clean code examples 🧰 मुफ़्त PDF और फ़ोटो टूल्स →
🏆 Project – पूरी Website बनाएँ  ·  Lesson 33

Project: Dynamic Pages के साथ Courses

इस part में क्या जुड़ेगा

📁 app/courses/
  page.jsx           # list + ?q= search (dynamic ƒ)
  CourseSearch.jsx   # 'use client' search box
  [slug]/page.jsx    # detail page (SSG ●, 6 pages)
तीन files

Step 1: Courses list page

JSX – app/courses/page.jsx
import CourseCard from '@/components/CourseCard'
import { searchCourses } from '@/lib/courses'
import CourseSearch from './CourseSearch'

export const metadata = {
  title: 'All Courses',
  description: 'Coding courses in C, Python, Web Development, Next.js, Android and MySQL with fees and duration.',
  alternates: { canonical: '/courses' },
}

// searchParams is a Promise in Next.js 16 — always await it.
export default async function CoursesPage({ searchParams }) {
  const { q = '' } = await searchParams
  const list = searchCourses(q)

  return (
    <div className="container section">
      <h1>Our courses</h1>
      <p className="lead">Search by name or level (Beginner / Intermediate).</p>
      <CourseSearch initial={q} />
      {list.length === 0 ? (
        <p>No course found for “{q}”.</p>
      ) : (
        <div className="grid" style={{ marginTop: 24 }}>
          {list.map((c) => (
            <CourseCard key={c.slug} course={c} />
          ))}
        </div>
      )}
    </div>
  )
}

Step 2: Search box (Client Component)

JSX – app/courses/CourseSearch.jsx
'use client'

import { useRouter } from 'next/navigation'
import { useState } from 'react'

// Client Component: keeps the typed text in state and updates the URL (?q=...).
export default function CourseSearch({ initial }) {
  const router = useRouter()
  const [text, setText] = useState(initial)

  function onSubmit(e) {
    e.preventDefault()
    router.push(text ? `/courses?q=${encodeURIComponent(text)}` : '/courses')
  }

  return (
    <form onSubmit={onSubmit} style={{ display: 'flex', gap: 10, marginTop: 16, maxWidth: 520 }}>
      <input
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder="Search courses… e.g. python"
        aria-label="Search courses"
        style={{ flex: 1, padding: '12px 14px', borderRadius: 12, border: '1px solid var(--line)', fontSize: '1rem' }}
      />
      <button className="btn" type="submit">Search</button>
    </form>
  )
}

Step 3: Course detail page

JSX – app/courses/[slug]/page.jsx
import Image from 'next/image'
import Link from 'next/link'
import { notFound } from 'next/navigation'
import JsonLd from '@/components/JsonLd'
import { courses, getCourse, rupees } from '@/lib/courses'
import { site } from '@/lib/site'

// Build one static HTML page per course at build time (SSG).
export function generateStaticParams() {
  return courses.map((c) => ({ slug: c.slug }))
}

// Unique title + description per course (very important for SEO).
export async function generateMetadata({ params }) {
  const { slug } = await params
  const course = getCourse(slug)
  if (!course) return { title: 'Course not found' }
  return {
    title: `${course.title} Course (${course.duration})`,
    description: `${course.summary} Fee ${rupees(course.fee)}. Level: ${course.level}.`,
    alternates: { canonical: `/courses/${course.slug}` },
    openGraph: { images: [course.image] },
  }
}

export default async function CoursePage({ params }) {
  const { slug } = await params
  const course = getCourse(slug)
  if (!course) notFound() // shows app/not-found.jsx with a real 404 status

  return (
    <div className="container">
      <p className="breadcrumb"><Link href="/">Home</Link> › <Link href="/courses">Courses</Link> › {course.title}</p>
      <div className="section" style={{ display: 'grid', gap: 28, gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', alignItems: 'start' }}>
        <Image src={course.image} alt={course.title} width={1200} height={675} priority quality={90} sizes="(max-width: 700px) 100vw, 560px" style={{ borderRadius: 16 }} />
        <div>
          <span className="badge">{course.level}</span>
          <h1 style={{ margin: '10px 0' }}>{course.title}</h1>
          <p className="lead">{course.summary}</p>
          <p><strong>Duration:</strong> {course.duration} · <strong>Fee:</strong> {rupees(course.fee)}</p>
          <h2>What you will learn</h2>
          <ul>{course.topics.map((t) => <li key={t}>{t}</li>)}</ul>
          <Link href={`/contact?course=${course.slug}`} className="btn">Enquire about this course</Link>
        </div>
      </div>
      <JsonLd
        data={{
          '@context': 'https://schema.org',
          '@type': 'Course',
          name: course.title,
          description: course.summary,
          provider: { '@type': 'Organization', name: site.name, sameAs: site.url },
          offers: { '@type': 'Offer', price: course.fee, priceCurrency: 'INR', category: 'Paid' },
          hasCourseInstance: { '@type': 'CourseInstance', courseMode: 'onsite', courseWorkload: `P${parseInt(course.duration, 10)}W` },
        }}
      />
    </div>
  )
}
generateStaticParamsbuild time पर 6 HTML pagesgenerateMetadataunique title, description, canonical, OG imagenotFound()गलत slug पर असली 404JsonLd Courseprice, provider, workload
एक page पर चार SEO layers

Search कैसे काम करता है

UserCourseSearch (client)courses/page.jsx (server)"python" type करके Search दबायाrouter.push('/courses?q=python')searchParams के साथ requestsearchCourses('python')1 card वाला HTML
URL-driven search: shareable और SEO friendly

Result

Search के साथ EduSite courses list
/courses
EduSite course detail page
/courses/nextjs

Checklist

  • ✅ /courses?q=beginner सिर्फ़ Beginner courses दिखाता है; /courses?q=xyz "No course found" दिखाता है।
  • ✅ /courses/abc status 404 लौटाता है।
  • ✅ हर detail page का अलग <title> है जैसे "Next.js Course (6 weeks) | EduSite"।
  • ✅ Build output में छह course pages के लिए ● दिखता है।
✅ Practice

"Level" filter (All / Beginner / Intermediate) links के रूप में जोड़िए जो ?q= set करें। lib/courses.js में सातवाँ course जोड़कर confirm कीजिए कि नया static page build होता है।

अक्सर पूछे जाने वाले प्रश्न (FAQ)

Search text को state की जगह URL में क्यों रखा?
/courses?q=python जैसा URL share, bookmark और नए tab में खोला जा सकता है, और server filtered list सीधे render करता है।
Enquire button /contact?course=slug पर क्यों जाता है?
Contact page searchParams पढ़कर form में वह course पहले से select कर देता है, ताकि enquiry सही course के साथ आए।
← Next.js Tutorial
🔗

Share this topic with a friend

यह topic किसी दोस्त को भेजें

Found it useful? Send it to a classmate learning the same thing.

अच्छा लगा? जो दोस्त यही सीख रहा है, उसे भेज दीजिए।

अगला topicProject: Markdown वाला Blog आपकी प्रगति: 33 / 37 (89%) · Lesson 34 / 37 आगे बढ़ें →

यह lesson कैसा लगा?

आपकी राय से हम content और बेहतर बनाते हैं।

अपना review और सुझाव लिखें (वैकल्पिक)

Review approve होने के बाद आपका नाम और शहर इसके साथ public दिख सकते हैं। Email या phone न लिखें।

▶ CodingEasily on YouTube

Video से सीखें – आसान Hindi में

चुनी हुई video: RDBMS भाग 3 – Class XI Commerce IT

▶ Channel subscribe करें