Static pages

Speed Build Marketplace provides a variety of components to help you quickly create SEO-optimized static pages, such as landing pages. The /components folder contains all the necessary elements, including hero sections, pricing tables, and FAQs. This modular approach allows you to easily customize and assemble your pages.

Additionally, the /libs/seo.js file is designed to help you set up SEO tags, ensuring your pages rank better on search engines like Google. It's crucial to customize these SEO tags to align with your content and target audience.

Example: Building a Simple Landing Page

Below is an example of how you can use the provided components to build a simple landing page:

// landingPage.js

import { getSEOTags } from '@/libs/seo'

export const metadata = getSEOTags({ canonicalUrlRelative: '/' })

export default function LandingPage() {
  return (
    <>
      <main className="min-h-screen p-12 pb-24 text-center" data-theme="dark">
        <section className="mx-auto max-w-xl space-y-8">
          <h1 className="text-3xl font-extrabold md:text-4xl">
            Food recipes you&apos;ll love 🥦
          </h1>
          <p className="text-base-content/80 text-lg leading-relaxed">
            Our AI will generate recipes based on your preferences. New recipes
            will be added every week!
          </p>
          <img
            src="https://images.unsplash.com/photo-1518843875459-f738682238a6?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=3484&q=80"
            alt="Vegetables"
            width={500}
            height={250}
            className="mx-auto rounded-lg"
          />
          <button className="btn btn-primary btn-wide">Get started</button>
        </section>
      </main>
    </>
  )
}

Authentication

Integrating authentication with Clerk is straightforward and essential for securing your application. Here’s how to set up Clerk authentication in your Speed Build Marketplace.

Setting Up Clerk Authentication

You need to set up Clerk's environment variables in your .env file to integrate authentication services. This ensures that your API keys are kept secure and are not hardcoded into your codebase.

  1. Go to your Clerk account and find the environment variables and add them to the .env file.

.env

NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_something
CLERK_SECRET_KEY=sk_test_something

Important: Make sure to use test keys (pk_test_something and sk_test_something) while developing. Replace these with your production keys only when deploying to a live environment.

Below is an example screenshot from the Clerk dashboard, highlighting where you can find these keys:

Screenshot from Clerk

Great, and once you have done this, then the auth configuration is fixed , as the ClerkProvider is already wrapping the whole application as seen below:

/app/layout.js

import { ClerkProvider } from "@clerk/nextjs";
...
export default function RootLayout({ children, params }) {
  return (
    <ClerkProvider>
      <html lang="en">
        <body>{children}</body>
        <GoogleTagManager gtmId={process.env.NEXT_PUBLIC_GTM_ID} />
      </html>
    </ClerkProvider>
  )
}
...

Models

Models are placed in the models folder. Below is an example of a user model schema using Mongoose:

import mongoose from 'mongoose'

// USER SCHEMA
const userSchema = mongoose.Schema(
  {
    name: {
      type: String,
    },
    email: {
      type: String,
    },
    image: {
      type: String,
    },
  },
  {
    timestamps: true,
  },
)

export default mongoose.models.User || mongoose.model('User', userSchema)

API Calls

All API calls are placed within the /app/api folder. Each route is represented by a folder, with a route.js file inside that defines the API logic for that route.

Example API Call

Here's a simplified example of an API call in route.js:

import { NextResponse } from 'next/server'
import connectMongo from '../../../libs/mongoose'
import User from '../../../models/User'
import { auth } from '@clerk/nextjs/server'

export async function GET(req) {
  try {
    const { userId } = auth()
    if (!userId) {
      return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
    }

    await connectMongo()
    const user = await User.findOne({ clerkUserId: userId })
    if (!user) {
      return NextResponse.json({ error: 'User not found' }, { status: 404 })
    }

    return NextResponse.json({
      _id: user._id,
    })
  } catch (error) {
    console.error('Error in GET /dashboard endpoint:', error)
    return NextResponse.json(
      { error: 'Internal Server Error' },
      { status: 500 },
    )
  }
}

Explanation

  • File Structure: The API call is placed in the /app/api/your-route/route.js file.
  • Authentication: The auth() function from @clerk/nextjs/server is used to authenticate the user.
  • Database Connection: The connectMongo() function is called to connect to the MongoDB database.
  • Model Usage: The User model is used to interact with the users collection in the database.
  • Response Handling: The NextResponse.json() method is used to send JSON responses back to the client.

Components

In Speed Build Marketplace, each page is typically composed of a server component and a client component. This architecture allows for better separation of concerns and efficient data handling.

Understanding Server and Client Components

Server Component

Server components are responsible for fetching data, handling authentication, and preparing any necessary data before rendering the client component. They do not include any interactive UI logic or state management.

Example

Let's take the Hero4Server component as an example. This component fetches a video URL and passes it down to the client component.

'use server'
import HeroClient from './HeroClient'

export default async function HeroServer() {
  const heroVideo =
    'https://videos.pexels.com/video-files/2853795/2853795-uhd_2560_1440_24fps.mp4'

  return (
    <div>
      <HeroClient heroVideo={heroVideo} />
    </div>
  )
}

Client Component

Client components handle the interactive part of your UI. They are responsible for rendering the UI, managing state, and handling user interactions.

Example

The HeroClient component displays the video and includes links to explore listings and start selling.

'use client'
import Link from 'next/link'

export default function HeroClient({ heroVideo }) {
  return (
    <div className="relative bg-white">
      <main>
        <div className="relative isolate">
          <video
            className="absolute inset-0 -z-10 h-full w-full object-cover"
            src={heroVideo}
            autoPlay
            muted
            loop
            playsInline
          />

          <div
            className="absolute left-1/2 right-0 top-0 -z-10 -ml-24 transform-gpu overflow-hidden blur-3xl lg:ml-24 xl:ml-48"
            aria-hidden="true"
          >
            <div
              style={{
                clipPath:
                  'polygon(63.1% 29.5%, 100% 17.1%, 76.6% 3%, 48.4% 0%, 44.6% 4.7%, 54.5% 25.3%, 59.8% 49%, 55.2% 57.8%, 44.4% 57.2%, 27.8% 47.9%, 35.1% 81.5%, 0% 97.7%, 39.2% 100%, 35.2% 81.4%, 97.2% 52.8%, 63.1% 29.5%)',
              }}
            />
          </div>
          <div className="relative overflow-hidden bg-gray-900/50">
            <div className="relative z-10 mx-auto max-w-7xl px-6 pb-16 pt-8 sm:pt-10 lg:px-8 lg:pt-12">
              <div className="mb-8 flex justify-start sm:mb-8 md:justify-center"></div>
              <div className="mx-auto max-w-2xl gap-x-14 lg:mx-0 lg:flex lg:max-w-none lg:items-center">
                <div className="relative w-full max-w-xl lg:shrink-0 xl:max-w-2xl">
                  <h1 className="text-4xl font-bold tracking-tight text-gray-900 text-white sm:text-8xl">
                    Buy and Sell Sneakers
                  </h1>
                  <p className="mt-6 text-lg leading-8 text-gray-600 text-white sm:max-w-md lg:max-w-none">
                    Buy and sell sneakers easily at this new marketplace.
                  </p>
                  <div className="mt-10 flex flex-col items-start gap-y-2 sm:flex-row sm:items-center sm:gap-x-6">
                    <Link
                      href="/listings"
                      className="rounded-md bg-green-600 px-5 py-3 text-base font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600"
                    >
                      Explore all listings now
                    </Link>

                    <div className="w-full sm:flex-1">
                      <Link
                        href="/sign-up"
                        className="text-sm font-semibold leading-6 text-gray-900 text-white"
                      >
                        Or start selling <span aria-hidden="true">→</span>
                      </Link>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </main>
    </div>
  )
}

Dashboard Pages

Dashboard pages in the Speed Build Marketplace are organized within the /app/(dashboard) directory. Each page is placed in a folder named after the route and consists of a page.js file (the server component) and a client component.

(Dashboard) Server Component

The server component is responsible for fetching data, handling authentication, and preparing any necessary data before rendering the client component. Here’s an example of a page.js file:

import 'server-only'
import DashboardPage from './dashboardPageClientSide.js'
import { auth } from '@clerk/nextjs/server'
import { Suspense } from 'react'

const fetchUserData = async () => {
  const { getToken } = auth()
  try {
    const token = await getToken()

    if (!token) {
      console.error('Token not found')
      return { redirect: '/sign-in' }
    }

    const headers = {
      Authorization: `Bearer ${token}`,
    }

    const response = await fetch(process.env.NEXT_PUBLIC_DASHBOARD, {
      headers,
    })

    if (response.status === 401) {
      return { redirect: '/' }
    }

    if (response.ok) {
      const data = await response.json()
      return data
    } else {
      console.error('Failed to fetch user data')
    }
  } catch (error) {
    console.error('Error fetching user data:', error)
  }
}

export default async function DashboardPageServerSide() {
  const data = await fetchUserData()

  const { firstName } = data || {}

  return (
    <Suspense fallback={<div>Loading...</div>}>
      <DashboardPage firstName={firstName} />
    </Suspense>
  )
}

(Dashboard) Client Component

The client component handles the interactive part of the UI, including rendering the UI, managing state, and handling user interactions. Here’s an example of a client component:

'use client'

import { useSearchParams } from 'next/navigation'
import { useState } from 'react'
import SuccessFullAlertComponent from '../../../Components/DashboardComponents/SuccessfullAlertComponent/SuccessfullAlertComponent.js'
import ActionsComponent from '../../../Components/DashboardComponents/ActionsComponent/ActionsComponent.js'

export default function DashboardPageClientSide({ firstName }) {
  const searchParams = useSearchParams()
  const emailVerified = searchParams.get('emailVerified')
  const [showWelcomeMessage, setShowWelcomeMessage] = useState(true)

  const formattedFirstName = firstName
    ? `${firstName.charAt(0).toUpperCase()}${firstName.slice(1).toLowerCase()}`
    : ''

  return (
    <>
      {emailVerified && (
        <SuccessFullAlertComponent message={'Email verified'} />
      )}
      {showWelcomeMessage && (
        <SuccessFullAlertComponent
          message={formattedFirstName ? `Welcome!` : 'Welcome!'}
          onDismiss={() => setShowWelcomeMessage(false)}
        />
      )}
      <div className="mt-3">
        <ActionsComponent />
      </div>
    </>
  )
}

Explanation

File Structure: The API call is placed in the /app/api/your-route/route.js file.

Authentication: The auth() function from @clerk/nextjs/server is used to authenticate the user.

Database Connection: The connectMongo() function is called to connect to the MongoDB database.

Model Usage: The User model is used to interact with the users collection in the database.

Response Handling: The NextResponse.json() method is used to send JSON responses back to the client.

Client Component: The client component handles the UI and interactions, using hooks like useState, useUser, and useLocale for state management and localization.

This structure helps maintain a clear separation of concerns, with server components focusing on data fetching and authentication, and client components managing the user interface and interactions.