For this tutorial, I will show you how to build a marketplace app using Next.js, Cosmic, and Stripe. After we are done, you will be equipped with a foundation to build your own projects using these powerful tools. Let’s get started. Bonus features include deploying to Vercel, dark and light mode, and using to measure the performance of the website’s , which dramatically increases the user's experience and in SEO search results (this app's Lighthouse score displayed below). Lighthouse core web vitals metrics website visibility Guys, if you like it please on . upvote Product Hunt Tools we’ll be using Next.js - scalable and high-performance framework for modern web development. Provides a large set of features, such as hybrid rendering, route prefetching, automatic image optimization, and internationalization, out of the box. React.js Cosmic - fast, fully managed headless CMS that enables us to quickly manage and create website content including UGC (user-generated content). Stripe - payments infrastructure that provides API tools to receive one-time and subscription payments. TL;DR Check out the code View the live demo Install the app template First, we need to provide architecture and design Designing is fundamentally about taking things apart, in such a way that they can be put back together. Separating things into things that can be composed that's what design is. — . Rich Hickey stands for model-view-controller software design pattern MVC Here's what each of those components means: : The backend that contains all the data logic. Cosmic Object Types ( help us create dynamic, scalable data structures and the essence of their transformations is independent of the outer world. Model Multiple or Singleton) : The frontend or graphical user interface (GUI). For it, we use React, which is described as the “ layer in the MVC. View (UI) V” : The brains of the application control how data is displayed. We use React’s Context API to decouple the state from the presentation and facilitate business logic re-use. Controller View MVC pattern is used for modern web applications because it allows the application to be scalable, maintainable, and easy to expand. Setting up a project with Next.js sets out to improve two things: and is a complete toolkit to build blazing fast React apps. Next.js offers an abstraction that solves the most common, mundane, and complex tasks like routing, internalization, and image optimization. Next.js developer and user experiences By default, Next.js pre-renders every page. This means that Next.js , instead of having it all done by client-side JavaScript. Pre-rendering can result in better performance and . generates HTML for each page in advance SEO Next.js has two forms of pre-rendering: and . Static Generation (SSG) Server-side Rendering (SSR) is the pre-rendering method that generates the HTML at . The pre-rendered HTML is then on each request. Static Generation build time reused is the pre-rendering method that generates the HTML on . Server-side Rendering each request In our project, we use for dynamic data and pre-render pages that will always be up-to-date. Server-side Rendering To get started, we'll create a Next.js app that includes tooling and configurations. For this tutorial, you'll need Node.js 12.22.0 or a later version. Open the terminal, paste or type npx create-next-app@latest unft-marketplace # or yarn create next-app unft-marketplace Change into the new directory and install the dependencies using a package manager like , , or and then start the app in the development mode: npm yarn pnpm cd unft-marketplace npm install cosmicjs stripe @stripe/stripe-js sass npm run dev # or yarn dev To use the template UI you need to clone it on . Open the terminal, paste or type this code to install all dependencies, and run it. GitHub git clone https://github.com/cosmicjs/unft-marketplace cd unft-marketplace yarn install yarn dev Open http://localhost:3000 in your browser to see the ascetic home page. Managing API keys/secrets with Next.js When working with API keys and secrets, we need to make sure we keep them secret and out of version control, while conveniently making them available as variables. You'll need to create a .env file at the root of the project. Log in to Cosmic and from takes the following values: .env Bucket Settings > API Access NEXT_PUBLIC_COSMIC_BUCKET_SLUG=your_cosmic_slug NEXT_PUBLIC_COSMIC_READ_KEY=your_cosmic_read_key COSMIC_WRITE_KEY=your_cosmic_write_key NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=your_stripe_key STRIPE_SECRET_KEY=your_stripe_secret_key The prefix automatically exposes this variable to the browser. Next.js will insert the value for these into the publicly viewable source code at build/render time. Therefore make sure to not use this prefix for secret values! In the same way login to Stripe and section take your . NEXT_PUBLIC_ for developers keys Next.js API routes Next.js API Routes to create API endpoints inside the Next.js app. They will be deployed as (also known as ). In our case, for filtering Cosmic content by parameters we’ll create a file called in directory that has the following format: Serverless Functions Lambdas filter.js pages/api //pages/api/filter import Cosmic from 'cosmicjs'; const bucket = Cosmic().bucket({ // Set these values, found in Bucket > Settings after logging in at https://app.cosmicjs.com/login slug: '', read_key: '', } ) export default async function filterHandler(req, res) { const { query: {min, max, color, category, search} } = req; let queryParam = {}; if(typeof min !== 'undefined' || typeof max !== 'undefined') { queryParam = { ...queryParam, "metadata.price": {"$gte": typeof min !== 'undefined' ? Number(min) : 1, "$lte": typeof max !== 'undefined' ? Number(max) : 1000000000 },} } if(typeof color !== 'undefined') { queryParam = { ...queryParam, "metadata.color": color,} } if(typeof category !== 'undefined') { queryParam = { ...queryParam, "metadata.categories": category,} } if(typeof search !== 'undefined') { queryParam = { ...queryParam, "title": { "$regex": search, "$options": "i" },} } const params = { query: { ...queryParam, type: 'products', }, props: 'title,slug,metadata,created_at', } try { const data = await bucket.getObjects(params); res.status(200).json(data); } catch (error) { res.status(404).json(error); } } Note that: is an instance of http.IncomingMessage, plus some pre-built middlewares. req is an instance of http.ServerResponse, plus some helper functions. res Cosmic features overview and integration is a great headless CMS that enables us to model, manage, and store our content and media in a convenient hosted dashboard, then fetch our content using available API tools. The API provides search and filtering capabilities out of the box which makes building search and filter features in our app easy. . Cosmic Cosmic provides full modern functionality and features for creating interactive, independent, and dynamic websites - Build out the schema, models, and controllers for the API from the admin dashboard. Cosmic offers both a API for our convenience. Customizable API REST and GraphQL - Call back anywhere you need, to get the functionality you want, out of the box. - allow you to find the correct asset with your data by . You can use some advanced queries to create more granular search capabilities. Webhooks Queries searching, filtering, and sorting - You can enable user-generated content in your app using the Cosmic API. Users can upload and create interactive content within your defined data models. Cosmic is not only a fully-managed content management system, but the API can be used to create user-generated content experiences as well. Add Media and Object Getting started with Cosmic The first step is creating a free Cosmic account into Cosmic Dashboard and a new Project. Let's select the "Start from scratch" option. Cosmic Object Types ( help us create dynamic, scalable, reusable data structures and our content model. Creating model sections, easy from scratch, defines the "Metafields" in the "Content Model". For a reusable model used across multiple Objects choose . For our project, we will create a reusable Object Type model using with . This will enable us to add or remove to enable searching, and filtering by category. Multiple or Singleton) Multiple Object Type Products Multiple Object Relationships Categories Products Categories and After creating the content model for our Products, you can begin adding Products from the dashboard like this: You can define other content models by creating Object Types following this schema design: Singleton for a unique model, Multiple for reusable models. Using Cosmic , you can quickly find the specific content according to criteria. queries search, filter, and sorting User-generated content Using the Cosmic capabilities to logged-in users can upload and create their own form-submitted content. For our use case, the user can create new to add to the marketplace for sale. Add Media and Objects, Products Uploading files and dynamic content to Next.js requires API Routes as we will need to store secret keys securely in server-side environment variables. Here is a brief overview of how to upload files with React and Next.js API routes using , , and the Cosmic method. formidable fs bucket.addMedia //api/upload.js import Cosmic from 'cosmicjs'; import formidable from 'formidable'; import fs from "fs"; const bucket = Cosmic().bucket({ slug: process.env.NEXT_PUBLIC_COSMIC_BUCKET_SLUG, read_key: process.env.NEXT_PUBLIC_COSMIC_READ_KEY, write_key: process.env.COSMIC_WRITE_KEY, }); export const config = { api: { bodyParser: false, }, }; export default async function uploadHandler( req,res ) { const form = new formidable.IncomingForm({ multiple: false }); try { form.parse( req, async ( err, fields, files ) => { if (err) return reject(err); const cosmicRes = await saveFile(files.file); res.status( 200 ).json(cosmicRes); } ); } catch (error) { res.status(404).json(error.message) } } const saveFile = async ( file ) => { const filedata = fs.readFileSync( file?.filepath ); const media_object = { originalname: file.originalFilename, buffer: filedata }; try { // Add media to Cosmic Bucket const cosmic_res = await bucket.addMedia({ media: media_object } ); await fs.unlinkSync(file?.filepath); return cosmic_res; } catch (error) { console.log(error); return; } }; Stripe overview and integration provides a way to capture payments in your application. Much like Cosmic has done all of the heavy liftings for content infrastructure, Stripe provides the e-commerce infrastructure and tools to build delightful payment experiences for your customers, irrespective of the service or product being sold. Stripe Due to PCI compliance requirements, the Stripe.js library has to be loaded from Stripe's servers. This creates a challenge when working with server-side rendered apps, as the window object is not available on the server. To help us manage that complexity, Stripe provides a that allows importing Stripe.js like an ES module: loading wrapper //lib/getStripe import { loadStripe } from '@stripe/stripe-js'; let stripePromise; const getStripe = () => { if(!stripePromise) { stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY); } return stripePromise; } export default getStripe; Stripe.js is loaded as a side effect of the statement. import '@stripe/stripe-js' Create the Stripe checkout session Add an endpoint on your server that creates a . A Checkout Session controls what your customer sees on the payment page such as line items, the order amount and currency, and acceptable payment methods. Also, you can enable or disable payment methods directly in the . Checkout Session Stripe Dashboard //api/stripe import Stripe from 'stripe'; const stripe = new Stripe( process.env.STRIPE_SECRET_KEY ); export default async function handler(req, res) { if (req.method === 'POST') { try { const params = { submit_type: 'pay', mode: 'payment', payment_method_types: ['card'], billing_address_collection: 'auto', shipping_options: [ { shipping_rate: 'shr_1L4pafH6oGDppJjV9MrYC7z0' }, { shipping_rate: 'shr_1L4pn4H6oGDppJjVBL7vPTk1' }, ], line_items: req.body.map((item) => { const img = item.metadata.image.imgix_url; return { price_data: { currency: 'usd', product_data: { name: item.title, images: [img], }, unit_amount: item.metadata.price * 100, }, adjustable_quantity: { enabled:true, minimum: 1, }, quantity: item.quantity } }), success_url: `${req.headers.origin}/`, cancel_url: `${req.headers.origin}/`, } // Create Checkout Sessions from body params. const session = await stripe.checkout.sessions.create(params); res.status(200).json(session); } catch (err) { res.status(err.statusCode || 500).json(err.message); } } else { res.setHeader('Allow', 'POST'); res.status(405).end('Method Not Allowed'); } } Next steps Though this marketplace app is mainly a proof of concept to learn about these modern solutions, it is a good starter to build on. There is potential to add new features such as: User account page to display orders User profile page to show off purchased jpegs Comment system to capture reviews and conversations Bitcoin / Lightning option for making purchases Conclusion I hope you enjoyed this tutorial exploring how to use powerful API-first tools to build a full-featured marketplace application. This dynamic, functional, customizable, and fully integrated uNFT marketplace, powered by can be customized for other types of businesses as well. Please feel free to , and use it how you prefer! Next.js, Cosmic, and Stripe fork the code