Skip to content
Public template

About

NUSX Webapp Templates

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

69 Commits

Folders and files

Repository files navigation

Starter Kit

Looking for the older version of Starter Kit? Check out the v2 branch.

A technical kit to quickly build new products from Open Government Products, Singapore.

Features

  • 🗼 Monorepo setup with Turborepo
  • 🧙‍♂️ E2E typesafety with tRPC v11
  • ⚡ Full-stack React with Next.js v16
  • 🌈 Database with Prisma
  • ⚙️ VSCode extensions
  • 🎨 Oxlint + Oxfmt
  • 💚 CI setup using GitHub Actions:
  • 🔐 Env var validation

Project structure

.github
  └─ workflows
        └─ CI with pnpm cache setup
.vscode
  └─ Recommended extensions and settings for VSCode users
.oxfmtrc.jsonc
  └─ Oxfmt formatter configuration
apps
  └─ web
      ├─ Next.js 16
      ├─ React 19
      ├─ Tailwind CSS v4
      └─ E2E Typesafe API Server & Client
packages
  ├─ common
  │   └─ Shared utilities and types
  ├─ db
  │   └─ Typesafe db calls using Prisma + Kysely
  ├─ logging
  │   └─ Shared logging utilities
  ├─ redis
  │   └─ Redis client and utilities
  ├─ ui
  │   └─ UI component library using react-aria-components + @opengovsg/oui
  └─ validators
      └─ Shared Zod validation schemas
tooling
  ├─ github
  │   └─ Shared GitHub Actions workflows
  ├─ oxlint
  │   └─ shared Oxlint configuration
  ├─ storybook
  │   └─ shared Storybook configuration
  ├─ tailwind
  │   └─ shared tailwind theme and configuration
  └─ typescript
      └─ shared tsconfig you can extend from

Working on your product

You may work on the codebase with:

Quick Start

Copy this template using Use this template button on GitHub > Create a new repository.

To change the repository name in the codebase, run the following command after cloning:

# Replace `@acme` (case-sensitive search) monorepo scope with your desired scope (e.g. `@myprojectname`)
grep -rl '@acme' --exclude='*.md' --exclude-dir='.git' --exclude-dir='node_modules' . | xargs sed -i '' 's/@acme/@<your_project_name>/g'

# Replace `Starter Kit` (case-sensitive search) with your project name with proper casing
grep -rl 'Starter Kit' --exclude='*.md' --exclude-dir='.git' --exclude-dir='node_modules' . | xargs sed -i '' 's/Starter Kit/<your_project_name>/g'

Then, follow the instructions to get started.

Using GitHub Codespaces

Follow the official GitHub guide for developing with a codespace.

Using your local developer environment

In summary:

Running the app locally

1. Setup dependencies

# Install dependencies
pnpm i

# Configure environment variables
# There is an `.env.example` in the root directory you can use for reference
cp .env.example .env

# Start docker containers
# use `docker compose down` to stop the containers afterwards
docker compose up -d

# Push the Prisma schema to the database
pnpm db:push

Optionally set POSTMAN_API_KEY to send login OTP emails via Postman. If not set, OTP emails will be logged to the console instead.

Retrieving client-side environment variables in code

⚠️ When adding client-only environment variables in NextJS, you must prefix the variable with NEXT_PUBLIC_ to ensure that the variable is exposed to the browser. For example, if you want to add a variable called MY_ENV_VAR, you should add it to your .env file as NEXT_PUBLIC_MY_ENV_VAR.

You will also need to update the various environment files (like apps/web/src/env.ts or packages/db/src/env.ts) to explicitly reference the variable so NextJS will correctly bundle the environment variable into the client-side bundle.

2. Adding a new package

To add a new package, simply run pnpm turbo gen init in the monorepo root. This will prompt you for a package name as well as if you want to install any dependencies to the new package (of course you can also do this yourself later).

The generator sets up the package.json, tsconfig.json and a index.ts, as well as configures all the necessary configurations for tooling around your package such as formatting, linting and typechecking. When the package is created, you're ready to go build out the package.

Deployment

Follow these instructions if you are familiar with building applications, and/or are in a hurry to prepare an environment to work on your product.

If you are new, see our Getting Started guide.

If you are exploring what else you can do with Starter Kit, a more comprehensive set of documentation, including guides and tutorials, can be found here.

The Vercel deployment outlined below is optimised for quick prototyping and development. It is not recommended for serious production use.

For serious production use cases, consider directly deploying Starter Kit to AWS. We provide a command line interface (CLI) tool (for internal use only) that can help you deploy Starter Kit to a standardised AWS infrastructure.

Nonetheless, on low-risk production use cases, Vercel can still be used as a production deployment. We have a document on when one might want to migrate to AWS here.

Deploy to Vercel

Let's deploy the Next.js application to Vercel. If you've never deployed a Turborepo app there, don't worry, the steps are quite straightforward. You can also read the official Turborepo guide on deploying to Vercel.

  1. Create a new project on Vercel, select the apps/web folder as the root directory. Vercel's zero-config system should handle all configurations for you.

  2. Add the prerequisite environment variables outlined in the Prerequisites section.

  3. Done! Your app should successfully deploy.

Prerequisites

The deployment needs a few environment variables to be set for it to function. They are:

Name What It Is Example
DATABASE_URL The connection string for your database. This should have been obtained from Neon postgresql://user:pass@xyz.ap-southeast-1.aws.neon.tech/app?sslmode=require
POSTMAN_API_KEY An API key to send email via Postman asdfn_v1_6DBRljleevjsd9DHPThsKDVDSenssCwW9zfA8W2ddf/T
SESSION_SECRET A sequence of random characters used to protect session identifiers, generated by running pnpx uuid from your terminal 66a21b98-fb17-4259-ac4f-e94d303ac894

One-click deploy

Deploy with Vercel

Deploy to a VM (pm2, zero-downtime builds)

For a self-hosted VM, run the app under pm2 with apps/web/ecosystem.config.cjs (cluster mode, WEB_INSTANCES workers, default 2; process name from PM2_APP_NAME, default web). It loads the repo-root .env with Node's --env-file.

Don't run pnpm build in the live checkout: next build wipes its output directory as soon as it starts, so the site serves 500s for the whole build. Deploy with scripts/deploy-build.sh instead:

  1. builds into a fresh apps/web/.next-<utc-timestamp>-<sha> (Next's distDir comes from NEXT_DIST_DIR, default .next);
  2. stops unless the new directory has BUILD_ID and static/, so a failed build leaves the live one untouched;
  3. atomically repoints the apps/web/.next symlink at it (ln -sfn to a temp name, then mv -T). The first time, an existing real .next directory is moved aside to .next-<ts>-legacy;
  4. keeps the newest DEPLOY_KEEP_BUILDS build directories (default 3, minimum 2) and never prunes the active one.

When pm2 starts, ecosystem.config.cjs resolves the .next symlink and pins each worker's NEXT_DIST_DIR to that directory, because Turbopack bakes distDir into the server chunks. Running workers never see the swap. The new build goes live when you restart pm2, which costs a ~1-2s blip:

git pull --ff-only
pnpm install --frozen-lockfile
pnpm -F @acme/db generate
pnpm -F @acme/db migrate:deploy
bash scripts/deploy-build.sh
pm2 delete web; pm2 start apps/web/ecosystem.config.cjs && pm2 save

Use pm2 delete + pm2 start, not pm2 reload. A reload keeps the old workers' resolved Next binary and environment.

Rollback to a kept build:

cd apps/web
ls -d .next-*/
ln -sfn .next-<previous> .next.swap && mv -T .next.swap .next
pm2 delete web; pm2 start ecosystem.config.cjs && pm2 save

Rolling back the build doesn't roll back the database or node_modules.

Migrations must be backward-compatible (expand/contract). Migrations run before the new build goes live, so for the whole build the old code serves traffic against the new schema. Add nullable or defaulted columns and new tables first. Drop or rename only in a later release, once no deployed code reads them. A migration that breaks the old code turns the build window back into an outage.

Known gap: pnpm install and prisma generate still run in the live checkout. Running workers have usually loaded what they need already, but on a deploy that changes pnpm-lock.yaml, a code path they haven't loaded yet can pick up the new dependency versions before the restart.

References

The stack originates from create-t3-app and create-t3-turbo with some additional OGP-specific tweaks.

Credits

Forked from opengovsg/starter-kit at commit 65cdbbc on 2026-05-04. Original work by Open Government Products, Singapore.

About

NUSX Webapp Templates

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages