Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
114 commits
Select commit Hold shift + click to select a range
4aecadb
App Router Migration (#221)
steven-tey Jun 29, 2023
5d83d9e
workaround
steven-tey Jun 29, 2023
7e230a2
remove dark mode from Tremor
steven-tey Jun 29, 2023
b2b6c9f
added prettier
steven-tey Jun 29, 2023
d75e0f2
return null for generateMetadata
steven-tey Jun 29, 2023
41b6445
fixed revalidateTag bug
steven-tey Jun 29, 2023
e850abf
fixed revalidateTag (was revalidating the wrong ones)
steven-tey Jun 29, 2023
01acf25
small fix
steven-tey Jun 29, 2023
2cf60e1
Added custom 404 pages
steven-tey Jun 29, 2023
1cb1ace
add router.refresh() when creating site
steven-tey Jun 29, 2023
2c96d28
added va track
steven-tey Jun 29, 2023
4a429cc
move not-found to page level, added metadatabase
steven-tey Jun 29, 2023
5425bea
updated login page
steven-tey Jun 29, 2023
f0a45ab
temp fix for server action error obfuscated in prod
steven-tey Jun 29, 2023
5f28d5d
remove color selector, updated github auth env var names
steven-tey Jun 30, 2023
5ceb766
Update README.md
steven-tey Jun 30, 2023
17f46dc
edit ctas
steven-tey Jun 30, 2023
40a52ed
refresh router when delete site, upgrade next to latest
steven-tey Jun 30, 2023
5cffd0b
router.refresh when delete post
steven-tey Jun 30, 2023
d2fbdb4
fix how we handle errors with Server Actions
steven-tey Jun 30, 2023
42e64cd
updated deploy button
steven-tey Jun 30, 2023
30563bc
Update login-button.tsx
steven-tey Jun 30, 2023
d42d3b4
patch fix revalidateTag
steven-tey Jun 30, 2023
1a7e4db
update next to canary
steven-tey Jun 30, 2023
107af11
fix revalidate tags syntax
steven-tey Jun 30, 2023
1d074c2
fix cache keys
steven-tey Jun 30, 2023
8d9d664
test tarball fix
steven-tey Jun 30, 2023
137e33e
added report abuse button, upgraded next to canary
steven-tey Jun 30, 2023
5fa14e3
removed file based metadata
steven-tey Jun 30, 2023
1970167
added dynamic sitemap
steven-tey Jun 30, 2023
90b134c
bug fixes and improve UX
steven-tey Jul 4, 2023
06fb6ec
hide sidebar on path change
steven-tey Jul 4, 2023
e5cd134
redirect vercel.pub to blog post
steven-tey Jul 4, 2023
6453d5e
fixed infinite loop in editor
steven-tey Jul 4, 2023
a01298e
revalidate metadata on site create and delete too
steven-tey Jul 4, 2023
8ac049f
override default CMD+S behavior
steven-tey Jul 4, 2023
5b8cc4d
change editor autoFocus to title
steven-tey Jul 4, 2023
e4f18e5
fixed image uploader
steven-tey Jul 4, 2023
63006ba
Added dark mode 🥳 (#228)
steven-tey Jul 4, 2023
b63e3d6
Added dark mode for client pages too
steven-tey Jul 4, 2023
935d227
small fixes
steven-tey Jul 5, 2023
7794f0e
upgrade react-tweet
steven-tey Jul 5, 2023
20643af
fix styles
steven-tey Jul 5, 2023
640ff7b
updated CTAs
steven-tey Jul 5, 2023
99756cb
await revalidateTag
steven-tey Jul 5, 2023
70d0ad8
fix login page dark mode styles
steven-tey Jul 5, 2023
af3b8ac
fix prisma
steven-tey Jul 5, 2023
b97a867
Create opengraph-image.png
steven-tey Jul 5, 2023
8eca7d5
Update README.md
steven-tey Jul 5, 2023
ae23480
added rate-limiting with Vercel KV
steven-tey Jul 5, 2023
e9e5719
Merge branch 'main' of https://github.com/vercel/platforms
steven-tey Jul 5, 2023
8f81acd
fix incorrect CNAME
steven-tey Jul 5, 2023
82463bf
fix dark mode author name styles
steven-tey Jul 5, 2023
3e21a46
Add tremor link to README.md (#235)
christopherkindl Jul 6, 2023
9c329e4
fix instances of vercel.pub
steven-tey Jul 6, 2023
6a00df4
Fix markdown links (#239)
lfades Jul 6, 2023
8cf1ad9
Fix tweet styles (#241)
lfades Jul 7, 2023
2a894e5
Update .env.example
steven-tey Jul 7, 2023
c42e919
fix thumbnail image
steven-tey Jul 7, 2023
d0bc8c2
Update .env.example (#237)
arig4m3r Jul 7, 2023
bec449b
upgrade next to canary
steven-tey Jul 10, 2023
505a8cf
Update next version to fix dependency resolution (#247)
olistic Jul 10, 2023
9120f47
added POSTGRES_URL_NON_POOLING
steven-tey Jul 10, 2023
b538188
Include title and desc in autocomplete context (#251)
DevMaxC Jul 10, 2023
d2ed0af
refactor import statement (#249)
zafarruzmatov Jul 10, 2023
5b9847b
chore: removing the fs dependency (#240)
nicholasgriffintn Jul 10, 2023
d91dfc6
Update all deps (#255)
steven-tey Jul 10, 2023
4691a62
Update fetchers.ts
steven-tey Jul 10, 2023
94a2eea
Updated root page, added tailwind-merge
steven-tey Jul 11, 2023
e2aa869
added tally form for blob beta
steven-tey Jul 19, 2023
befb025
Update README.md
steven-tey Jul 20, 2023
5dfa338
Update middleware.ts
steven-tey Jul 20, 2023
5a2a284
Update middleware.ts
steven-tey Jul 20, 2023
1c761dd
upgrade next to canary
steven-tey Jul 20, 2023
7ded19d
Merge branch 'main' of https://github.com/vercel/platforms
steven-tey Jul 20, 2023
b99d24a
Added `novel` SDK (#309)
steven-tey Sep 1, 2023
b6f2a5e
make TEAM_ID_VERCEL optional
steven-tey Sep 29, 2023
fe1e2c7
bump next to canary
steven-tey Sep 29, 2023
ffd9a8f
optional: add canonical URLs
steven-tey Sep 29, 2023
4cfd59c
fix: issue that causes root home page to 404 on vercel (#323)
chroxify Sep 29, 2023
ebb8954
domain from page params converted to decodeURIComponent (#313)
Adnan0061 Sep 29, 2023
76734c1
fix(prisma): Remove shadowDatabaseUrl and upgrade to newest Prisma (t…
janpio Sep 29, 2023
36a5a67
generateStaticParams
steven-tey Sep 29, 2023
2fa51dc
fix ts issue
steven-tey Sep 29, 2023
e9e5367
account for www.
steven-tey Sep 29, 2023
eeabcf1
import prisma
steven-tey Sep 29, 2023
ef6fc76
fix ts error
steven-tey Sep 29, 2023
5084746
Fox #288
steven-tey Sep 29, 2023
ca3bc27
fix ts error
steven-tey Sep 29, 2023
2435c66
fix generateStaticParams
steven-tey Sep 29, 2023
1ed55b1
fix
steven-tey Sep 29, 2023
a281c65
bump next-auth
steven-tey Sep 29, 2023
4708aea
fix: placeholder typo (#301)
0o001 Sep 29, 2023
a645f23
bump version
steven-tey Oct 3, 2023
c83b6ff
bump next.js version
steven-tey Oct 3, 2023
5947c92
revert to 13.5.4
steven-tey Oct 3, 2023
379b8da
revert
steven-tey Oct 3, 2023
2ba463a
change version
steven-tey Oct 3, 2023
3f4cbf3
fixed issue
steven-tey Oct 3, 2023
dbbc887
Replace deprecated props (#348)
ItzCrazyKns Nov 9, 2023
841619c
Subdomain Previews Implementation (#347)
steven-tey Nov 9, 2023
a634401
fix: (minor) double class (#284)
jpvalery Nov 14, 2023
ff88e60
Chore: collpase imports from the same library into a single statement…
TatisLois Nov 14, 2023
fdca323
Fix analytics Top Pages rendering bug (#331)
versecafe Nov 14, 2023
29e20e7
Update dependencies. (#353)
leerob Nov 14, 2023
7e33556
Migrate to Drizzle ORM (#404)
realmikesolo Jun 8, 2024
834ca43
update deploy button env link and text (#440)
ismaelrumzan Dec 5, 2024
299867c
Migrate to new, simplified example. (#451)
leerob May 8, 2025
0658ac1
Update README.md (#454)
yamz8 May 11, 2025
a2845dd
Small update to Middleware
leerob May 13, 2025
5bab8fb
Fix client error
leerob May 13, 2025
29cfcbe
Add Vercel Analytics and Speed Insights integration (#468)
dieDeMiguel Aug 14, 2025
d8d803f
Create main.yml
eevanlai-stack Sep 9, 2025
3a765ec
Create npm
eevanlai-stack Sep 9, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 0 additions & 32 deletions .env.example

This file was deleted.

3 changes: 0 additions & 3 deletions .eslintrc.json

This file was deleted.

1 change: 1 addition & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

28 changes: 28 additions & 0 deletions .github/workflows/npm-gulp.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: NodeJS with Gulp

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:
build:
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [18.x, 20.x, 22.x]

steps:
- uses: actions/checkout@v4

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}

- name: Build
run: |
npm install
gulp
46 changes: 40 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,41 @@
**/node_modules
**.next
**.env
**.DS_Store
**.vercel
**.git
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# env files (can opt-in for committing if needed)
.env*

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
4 changes: 0 additions & 4 deletions .vscode/settings.json

This file was deleted.

173 changes: 63 additions & 110 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,137 +1,90 @@
<p align="center">
<a href="https://demo.vercel.pub">
<img src="https://assets.vercel.com/image/upload/v1588805858/repositories/vercel/logo.png" height="96">
<h3 align="center">Platforms Starter Kit</h3>
</a>
</p>
# Next.js Multi-Tenant Example

<p align="center">
The <em>all-in-one</em> starter kit <br/>
for building platforms on Vercel.
</p>
A production-ready example of a multi-tenant application built with Next.js 15, featuring custom subdomains for each tenant.

<p align="center">
<a href="#introduction"><strong>Introduction</strong></a> ·
<a href="https://vercel.com/guides/nextjs-multi-tenant-application"><strong>Guide</strong></a> ·
<a href="https://demo.vercel.pub/"><strong>Demo</strong></a> ·
<a href="https://steven.vercel.pub/kitchen-sink"><strong>Kitchen Sink</strong></a> ·
<a href="#contributing"><strong>Contributing</strong></a>
</p>
<br/>
## Features

## Deploy Your Own
- ✅ Custom subdomain routing with Next.js middleware
- ✅ Tenant-specific content and pages
- ✅ Shared components and layouts across tenants
- ✅ Redis for tenant data storage
- ✅ Admin interface for managing tenants
- ✅ Emoji support for tenant branding
- ✅ Support for local development with subdomains
- ✅ Compatible with Vercel preview deployments

[Read the guide](https://vercel.com/guides/nextjs-multi-tenant-application) to learn how to deploy your own version of this template.
## Tech Stack

## Introduction
- [Next.js 15](https://nextjs.org/) with App Router
- [React 19](https://react.dev/)
- [Upstash Redis](https://upstash.com/) for data storage
- [Tailwind 4](https://tailwindcss.com/) for styling
- [shadcn/ui](https://ui.shadcn.com/) for the design system

Multi-tenant applications serve multiple customers across different subdomains/custom domains with a single unified codebase.
## Getting Started

For example, our demo is a multi-tenant application:
### Prerequisites

- Subdomain: [demo.vercel.pub](http://demo.vercel.pub)
- Custom domain: [platformize.co](http://platformize.co) (maps to [demo.vercel.pub](http://demo.vercel.pub))
- Build your own: [app.vercel.pub](http://app.vercel.pub)
- Node.js 18.17.0 or later
- pnpm (recommended) or npm/yarn
- Upstash Redis account (for production)

Another example is [Hashnode](https://vercel.com/customers/hashnode), a popular blogging platform. Each writer has their own unique `.hashnode.dev` subdomain for their blog:
### Installation

- [eda.hashnode.dev](https://eda.hashnode.dev/)
- [katycodesstuff.hashnode.dev](https://katycodesstuff.hashnode.dev/)
- [akoskm.hashnode.dev](https://akoskm.hashnode.dev/)
1. Clone the repository:

Users can also map custom domains to their `.hashnode.dev` subdomain:
```bash
git clone https://github.com/vercel/platforms.git
cd platforms
```

- [akoskm.com](https://akoskm.com/) → [akoskm.hashnode.dev](https://akoskm.hashnode.dev/)
2. Install dependencies:

This repository makes it easier than ever for creators to build their own platform.
```bash
pnpm install
```

## Template features
3. Set up environment variables:
Create a `.env.local` file in the root directory with:

Forget manually setting up CNAME records, wrestling with DNS, or making custom server rewrite rules with NGINX. With Vercel and the Platforms Starter Kit, you can focus on building the next big thing.
```
KV_REST_API_URL=your_redis_url
KV_REST_API_TOKEN=your_redis_token
```

- **Custom domains**: Subdomain and custom domains support with [Edge Functions](https://vercel.com/features/edge-functions) and the [Vercel Domains API](https://domains-api.vercel.app/).
- **Static generation with ISR**: Performance without sacrificing personalization, by combining [Incremental Static Regeneration](https://vercel.com/docs/concepts/next.js/incremental-static-regeneration) (ISR) and [Middleware](https://vercel.com/docs/concepts/functions/edge-functions#middleware). ISR allows you to create new content (with custom domains) on demand without needing to redeploy your application.
- **Uploading custom images**: Allow your customers to upload custom thumbnail images with our Cloudinary integration.
- **Static tweets**: Avoid [Cumulative Layout Shift](https://vercel.com/blog/core-web-vitals) (CLS) from the native Twitter embed by using our [static tweets implementation](https://static-tweets-tailwind.vercel.app/) (supports image, video, gif, poll, retweets, quote retweets, and more).
4. Start the development server:

## Examples of platforms
```bash
pnpm dev
```

Vercel customers like [Hashnode](https://vercel.com/customers/hashnode), [Super](https://super.so), and [Cal.com](https://cal.com) are building scalable platforms on top of Vercel and Next.js. There are multiple types of platforms you can build with this starter kit:
5. Access the application:
- Main site: http://localhost:3000
- Admin panel: http://localhost:3000/admin
- Tenants: http://[tenant-name].localhost:3000

### 1. Content creation platforms
## Multi-Tenant Architecture

These are content-heavy platforms (blogs) with simple, standardized page layouts and route structure.
This application demonstrates a subdomain-based multi-tenant architecture where:

> “With Vercel, we spend less time managing our infrastructure and more time delivering value to our users.” — Sandeep Panda, Co-founder, Hashnode
- Each tenant gets their own subdomain (`tenant.yourdomain.com`)
- The middleware handles routing requests to the correct tenant
- Tenant data is stored in Redis using a `subdomain:{name}` key pattern
- The main domain hosts the landing page and admin interface
- Subdomains are dynamically mapped to tenant-specific content

1. [Hashnode](https://hashnode.com)
2. [Mirror.xyz](https://mirror.xyz/)
3. [Read.cv](https://read.cv/)
The middleware (`middleware.ts`) intelligently detects subdomains across various environments (local development, production, and Vercel preview deployments).

### 2. Website & e-commerce store builders
## Deployment

No-code site builders with customizable pages.
This application is designed to be deployed on Vercel. To deploy:

By using Next.js and Vercel, [Super](https://super.so/) has fast, globally distributed websites with a no-code editor (Notion). Their customers get all the benefits of Next.js (like [Image Optimization](https://nextjs.org/docs/basic-features/image-optimization)) without touching any code.
1. Push your repository to GitHub
2. Connect your repository to Vercel
3. Configure environment variables
4. Deploy

1. [Super.so](https://super.so)
2. [Typedream](https://typedream.com)
3. [Makeswift](https://www.makeswift.com/)
For custom domains, make sure to:

### 3. B2B2C platforms

Multi-tenant authentication, login, and access controls.

With Vercel and Next.js, platforms like [Instatus](https://instatus.com) are able to create status pages that are *10x faster* than competitors.

1. [Instatus](https://instatus.com/)
2. [Cal.com](https://cal.com/)
3. [Dub](https://dub.sh/)

## Built on open source

This working demo site was built using the Platforms Starter Kit and:

- [Next.js](https://nextjs.org/) as the React framework
- [Tailwind](https://tailwindcss.com/) for CSS styling
- [Prisma](https://prisma.io/) as the ORM for database access
- [PlanetScale](https://planetscale.com/) as the database (MySQL)
- [NextAuth.js](https://next-auth.js.org/) for authentication
- [Vercel](http://vercel.com/) for deployment

We also have another [example](https://github.com/vercel/examples/tree/main/solutions/platforms-slate-supabase) of the Platforms Starter Kit that uses Supabase for the database and Slate.js for the text editor.

## Frequently Asked Questions

- **Should we be generating static webpages with `getStaticProps` and `getStaticPaths` at build time? It doesn't seem to be very scalable.**

For scale, we recommend using [Incremental Static Regeneration](https://nextjs.org/docs/basic-features/data-fetching/incremental-static-regeneration) instead. This basically means that instead of generating all pages at build time, you only specify a subset of pages and then generate the rest on the fly. Then when someone requests that page, all subsequent requests will be cached on the Vercel edge. You can also use [on-demand ISR](https://nextjs.org/docs/basic-features/data-fetching/incremental-static-regeneration#on-demand-revalidation) to programmatically invalidate caches per page every time someone makes a change to it, which is what we do [here](https://github.com/vercel/platforms/blob/1b2bd00055bbbdde8f2dcc89e0bdb2c3f8488f97/lib/api/post.ts#L243-L257).

- **Is it wise to be using the `/_sites/[site]` path to serve all static pages/website? Wouldn't that lead to a significant amount of load on a single Next.js server?**

The beauty about a serverless setup is you won’t have to worry about load since each request invokes a separate serverless function, and once it’s cached, you don’t invoke the server anymore (the page is served directly from the Vercel edge). Read more about the [Vercel Edge Network](https://vercel.com/docs/concepts/edge-network/overview) and [how caching works](https://vercel.com/docs/concepts/edge-network/caching).


## Caveats

- This template does not work with i18n, which is an [advanced feature in Next.js](https://nextjs.org/docs/advanced-features/i18n-routing).


## Contributing

- [Start a discussion](https://github.com/vercel/platforms/discussions) with a question, piece of feedback, or idea you want to share with the team.
- [Open an issue](https://github.com/vercel/platforms/issues) if you believe you've encountered a bug with the starter kit.

## Author

- Steven Tey ([@steventey](https://twitter.com/steventey))

## License

The MIT License.

---

<a aria-label="Vercel logo" href="https://vercel.com">
<img src="https://badgen.net/badge/icon/Made%20by%20Vercel?icon=zeit&label&color=black&labelColor=black">
</a>
1. Add your root domain to Vercel
2. Set up a wildcard DNS record (`*.yourdomain.com`) on Vercel
69 changes: 69 additions & 0 deletions app/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
'use server';

import { redis } from '@/lib/redis';
import { isValidIcon } from '@/lib/subdomains';
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
import { rootDomain, protocol } from '@/lib/utils';

export async function createSubdomainAction(
prevState: any,
formData: FormData
) {
const subdomain = formData.get('subdomain') as string;
const icon = formData.get('icon') as string;

if (!subdomain || !icon) {
return { success: false, error: 'Subdomain and icon are required' };
}

if (!isValidIcon(icon)) {
return {
subdomain,
icon,
success: false,
error: 'Please enter a valid emoji (maximum 10 characters)'
};
}

const sanitizedSubdomain = subdomain.toLowerCase().replace(/[^a-z0-9-]/g, '');

if (sanitizedSubdomain !== subdomain) {
return {
subdomain,
icon,
success: false,
error:
'Subdomain can only have lowercase letters, numbers, and hyphens. Please try again.'
};
}

const subdomainAlreadyExists = await redis.get(
`subdomain:${sanitizedSubdomain}`
);
if (subdomainAlreadyExists) {
return {
subdomain,
icon,
success: false,
error: 'This subdomain is already taken'
};
}

await redis.set(`subdomain:${sanitizedSubdomain}`, {
emoji: icon,
createdAt: Date.now()
});

redirect(`${protocol}://${sanitizedSubdomain}.${rootDomain}`);
}

export async function deleteSubdomainAction(
prevState: any,
formData: FormData
) {
const subdomain = formData.get('subdomain');
await redis.del(`subdomain:${subdomain}`);
revalidatePath('/admin');
return { success: 'Domain deleted successfully' };
}
Loading