Topmart is a full-featured E-commerce Product Listing Platform I built with Next.js, TypeScript, ShadCN and Tailwind CSS. It includes an administrator dashboard for managing products and a storefront for users to browse and purchase items. The platform is designed to be SEO-compliant, performant, and follows clean coding practices.
Topmart is split into two primary components:
Product Management: Users can add, edit, and delete products in real-time. All changes are immediately reflected on the storefront.
SEO Optimization: Comprehensive SEO strategies are implemented, including meta tags, dynamic metadata generation, and a sitemap.
Server side Rendering: I used React Server Components in the latest version of NextJS as of today- V14.2 (01-09-2024) Server-side rendering (SSR), and static site generation (SSG) are implemented differently in the app router than the pages router. getStaticProps
and getServerSideProps
are now deprecated.
Payment Integration: Paystack is integrated as the payment gateway to facilitate secure transactions.
Integration with my NextJS products API: I wanted to demonstrate my proficiency with Server Side Rendering by dynamically pre-rendering each page content on the server before sending it to the client as well as the SEO tags.
I built an API alongside the Admin dashboard using NextJs. The persistence layer is a MongoDB database hosted with mongoDB atlas, my ORM of choice is Prisma. I built this NextJS storefront separately so I'd require the minimum env credentials if you were to set up locally. Just the public API endpoint and the Paystack test Key.
You can check out the storefront at https://topmart.vercel.app .
To run Topmart locally, follow these steps:
Clone the Repository:
git clone https://github.com/yourusername/topmart.git
cd topmart
Create a .env
File:
Add the following environment variables to a .env
file in the root directory:
NEXT_PUBLIC_API_URL=https://topmart-admin.vercel.app/api/668f9ec5d8ac115da48ede86/
NEXT_PUBLIC_PAYSTACK_PUBLIC_KEY=pk_test_YOUR_PAYSTACK_TEST_KEY
You can obtain a Paystack test key by creating a Paystack account and navigating to the settings tab in the dashboard.
Install Dependencies:
npm install
Run the Development Server:
npm run dev
To build for production and start the server:
npm run build
npm start
generateMetadata
function were used to provide detailed SEO data for each page, improving search engine visibility. A sitemap and robots.txt file were also included to guide search engine crawlers.To ensure the app is SEO-compliant and highly discoverable, I implemented the following strategies
Comprehensive Metadata: A detailed metadata object was included in the root layout page to ensure all essential SEO tags are present on every page.
export const metadata: Metadata = {
metadataBase: new URL('https://topmart.vercel.app/'),
title: "Topmart Store",
description: "Your one-stop online store",
keywords: "online, store, shopping, electronics, fashion, home, goods, Topmart, yaba, mandilas, lagos, thrift, iphone, lambo",
openGraph: {
title: 'Topmart Store',
description: 'Your one-stop online store',
url: 'https://topmart.vercel.app/',
siteName: 'Topmart Store',
images: [
{
url: 'https://topmart.vercel.app/opengraph-image.png',
width: 800,
height: 600,
},
{
url: 'https://topmart.vercel.app/twitter-image.png',
width: 1800,
height: 1600,
alt: 'Twitter Image',
},
],
locale: 'en_US',
type: 'website',
},
robots: {
index: true,
follow: true,
nocache: false,
googleBot: {
index: true,
follow: true,
noimageindex: false,
'max-video-preview': -1,
'max-image-preview': 'large',
'max-snippet': -1,
},
},
}
Dynamic SEO Content: The generateMetadata
function was used for dynamic routes (products and billboards), allowing each product and billboard page to have unique, descriptive metadata. This includes the use of keywords, descriptions, and Open Graph tags tailored to the content of each page. i.e - in the billboards page (app/(routes)/billboard/[billboardId]/page.tsx) and also in the products page (app/(routes)/product/[productId]/page.tsx)
export async function generateMetadata({ params }: ProductPageProps): Promise<Metadata> {
const product = await getProduct(params.productId);
if (!product) {
return {
title: 'Product not found - Topmart Store',
description: 'The product you are looking for could not be found on Topmart Store.',
};
}
return {
title: `${product.name} - Topmart Store`,
description: `Buy ${product.name} on Topmart Store. ${product.description || "Discover high-quality products at the best prices."}`,
keywords: `${product.name}, ${product.category?.name || ''}, ${product.size?.name || ''}, ${product.color?.name || ''}, online shopping, nigeria, buy ${product.name}`,
openGraph: {
title: `${product.name} - Topmart Store`,
description: `Buy ${product.name} on Topmart Store. ${product.description || "Discover high-quality products at the best prices."}`,
url: `https://topmart.vercel.app/product/${params.productId}`,
images: product.images.map((image) => ({
url: image.url,
width: 800,
height: 600,
alt: product.name,
})),
},
};
}
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const defaultPages = [
{
url: baseUrl,
lastModified: new Date(),
changeFrequency: "daily",
priority: 1
},
{
url: `${baseUrl}cart`,
lastModified: new Date(),
changeFrequency: "daily",
priority: 0.9
}
];
const billboardIds = (await getBillboards()).map(billboard => billboard.id);
const productIds = (await getProducts({})).map(product => product.id)
const sitemap = [
...defaultPages,
...billboardIds.map((slug: string) => ({
url: `${baseUrl}billbord/${slug}`,
priority: 0.8
})),
...productIds.map((slug: string) => ({
url: `${baseUrl}product/${slug}`,
lastModified: new Date(),
changeFrequency: "daily",
priority: 0.7
}))
];
return sitemap;
}