This repository contains the source code for the official website as well as the official app of conveniat27, built with Next.js and Payload CMS.
- Core Technologies
- Prerequisites
- Getting Started
- Project Structure
- Key Concepts
- Code Quality & Conventions
- UI Component Library
- Environment Variables
- License
- Framework: Next.js (App Router)
- TRPC: tRPC (for type-safe API routes)
- CMS: Payload CMS (Headless, Self-hosted)
- Language: TypeScript (with strict type checking)
- UI: React, shadcn/ui, Tailwind CSS, Headless UI
- Icons: Lucide React
- Database: MongoDB (self-hosted), MinIO (S3-compatible object storage, self-hosted), PostgreSQL (self-hosted)
- PWA: Serwist (for Service Worker management)
- Code Quality: ESLint, Prettier
- Development Environment: Docker (Devcontainer)
Ensure you have the following installed on your system:
- Git
- Docker & Docker Compose
- An IDE that supports Devcontainers (e.g., VS Code with the Dev Containers extension, WebStorm).
- Clone the repository
- Copy the
.env.example
file to.env
and fill empty values. - Open the project using the provided devconatiner inside your IDE (VSCode or Webstorm are tested).
- Start Developing using the following commands:
The above command launches a local development server with hot-reloading enabled. You can open the website on
docker compose up --build
http://localhost:3000
.
- Install Dependencies:
pnpm install
- Start Development Server:
docker compose up --build
- Stop Development Server:
docker compose down
- Clear Database & Volumes: To completely reset the database and remove Docker volumes (useful for reseeding):
After running this, you'll need to restart the server with
docker compose down --volumes
docker compose up --build
to re-initialize and potentially re-seed the database based on Payload's configuration.
Once the development server is running, you can typically access the Payload CMS admin interface at:
http://localhost:3000/admin
(or your configured admin route)
The project structure is influenced by Next.js App Router conventions and principles from Bulletproof React, emphasizing modularity and maintainability.
public/ # Static assets (images, fonts, sw.js, etc.)
src/
|
+-- app/ # Next.js App Router: Layouts, Pages, Route Handlers
| |-- (entrypoint)/ # Entrypoint for the APP (manually localized)
| |-- (payload)/ # Routes related to Payload Admin UI
| |-- (frontend)/ # Routes for the main website frontend / app
|
+-- components/ # Globally shared React components
|
+-- config/ # Global application configurations (e.g., exported env vars)
|
+-- features/ # Feature-based modules (self-contained units of functionality)
| |-- service-worker/ # Serwist service worker logic
| |-- payload-cms/ # Payload CMS specific configurations, collections, globals, hooks
| +-- ... # Other features
|
+-- hooks/ # Globally shared React hooks
|
+-- lib/ # Globally shared utility functions, libraries, clients
|
+-- types/ # Globally shared TypeScript types and interfaces
|
+-- utils/ # Globally shared low-level utility functions
- Most application logic resides within the
src/features
directory. - Each sub-directory in
src/features
represents a distinct feature (e.g.,chat
,map
,payload-cms
). - Encapsulation: Code within a feature folder should primarily relate to that specific feature.
- Structure within Features: A feature can internally have its own
components
,hooks
,api
,types
,utils
subdirectories, scoped to that feature. - Import Restrictions: ESLint rules (
import/no-restricted-paths
ineslint.config.mjs
) enforce unidirectional dependencies:app
can import fromfeatures
and shared directories (components
,hooks
, etc.).features
cannot import fromapp
or shared directories.- Features generally should not import directly from other features, promoting loose coupling. Exceptions are
explicitly defined (e.g.,
payload-cms
andnext-auth
can be imported more broadly). - Shared directories (
components
,hooks
,lib
,types
,utils
) should not import fromapp
orfeatures
.
- Payload CMS Exception: The
payload-cms
feature is central and can be imported by other parts of the application as it defines the core data structures / content types used throughout the app.
This structure aids scalability, maintainability, and team collaboration by keeping concerns separated.
A core aspect of this project is that most frontend pages are dynamically generated based on data managed within Payload CMS.
- CMS Configuration (
src/features/payload-cms/payload.config.ts
,src/features/payload-cms/settings
): Defines data structures (Collections, Globals) and their fields. Collections might represent page types, blog posts, etc. - Routing (
src/app/(frontend)/[locale]/(payload-pages)/[...slugs]/page.tsx
): This dynamic route catches most frontend URL paths. - Route Resolution: The application resolves the incoming URL (
slugs
) against Collections and Globals defined in Payload CMS (via thesrc/features/payload-cms/routeResolutionTable.ts
). - Layout & Component Mapping: Once the corresponding CMS data is found for a URL, a specific page layout (
src/features/payload-cms/page-layouts
) is rendered. Complex CMS fields (like Blocks or Rich Text) are mapped to React components using converters (src/features/payload-cms/converters
).
This application utilizes Serwist (@serwist/next
) to implement Service Worker
functionality, enabling PWA features:
- Offline Access: Pre-cached pages (like the
/offline
page) and potentially other assets allow basic functionality when the user is offline. - Caching: Improves performance by caching assets and network requests.
- Reliability: Provides a more resilient user experience on flaky networks.
The service worker logic is defined in src/features/service-worker/index.ts
and configured in next.config.mjs
. It's
generally disabled in development unless ENABLE_SERVICE_WORKER_LOCALLY=true
is set.
Maintaining code quality and consistency is crucial.
The project enforces strict TypeScript settings (tsconfig.json
), including:
strict
, strictNullChecks
, noImplicitAny
, noUncheckedIndexedAccess
, exactOptionalPropertyTypes
, etc. This helps
catch errors at compile time and improves code reliability.
- ESLint (
eslint.config.mjs
): Used for identifying and reporting on patterns in JavaScript/TypeScript code. Includes rules fromeslint:recommended
,typescript-eslint
,unicorn
,react-hooks
,next/core-web-vitals
, and custom rules for conventions and import restrictions. - Prettier: Used for automatic code formatting to ensure a consistent style. Integrated via
eslint-plugin-prettier
. - Run Checks: (Ensure these scripts exist in your
package.json
)# Run ESLint checks and fix issues pnpm run lint
As mentioned in the Project Structure section, ESLint rules strictly enforce module boundaries to
maintain a clean and understandable architecture. Path aliases (@/*
, @payload-config
) defined in tsconfig.json
are
used for cleaner imports.
- shadcn/ui: Provides beautifully designed, accessible components built on Radix UI and
Tailwind CSS. Components are typically copied into the project (
src/components/ui
) rather than installed as a dependency. - Headless UI: Used for unstyled, accessible UI components providing underlying logic for elements like modals, dropdowns, etc.
- Lucide React: Provides a wide range of clean and consistent SVG icons.
- Configuration is managed via environment variables.
.env.example
serves as a template listing the required variables.- Create a
.env
file (copied from.env.example
) for local development. Never commit.env
files to Git. - Populate
.env
with necessary credentials (database URLs, API keys, secrets, etc.).
The easiest way to build the page into a production ready bundle is to use the provided Docker Compose file. This will build the Next.js application and Payload CMS, and prepare it for deployment.
docker compose -f docker-compose.prod.yml up --build
However, you can also build the application manually using the following commands.
Please ensure that you have deleted node_modules
, src/lib/prisma/*
, and .next
before running the commands to ensure a clean build.
Also make sure that you DON'T have any .env
file in the root of the project, as this will
cause issues with the build process.
# Export environment variables
export $(grep -v '^#' .env | grep '^NEXT_PUBLIC_' | xargs)
export BUILD_TARGET="production"
export NODE_ENV="production"
export DISABLE_SERVICE_WORKER="true" # speeds up build process (optional)
export PRISMA_OUTPUT="src/lib/prisma/client/"
# Install dependencies
pnpm install
# Create build info file
bash create_build_info.sh
# Generate Prisma client
npx prisma generate
# Build the Next.js application
pnpm next build
To analyze the bundle size of the Next.js application, you can use the next-bundle-analyzer
package.
Xou can run the following command to analyze the bundle size. This will generate a report and open it in
your default browser.
ANALYZE=true pnpm build
We follow a standard Git workflow for managing changes:
- Branching: Create a new branch for each (bigger) feature or bug fix.
- Use descriptive names (e.g.,
feature/new-header
,bugfix/fix-footer
). - For small changes, you can commit directly to the
dev
branch. - For larger features, create a feature branch from
dev
and merge it back when complete. - You are allowed to force push to your feature branches. However, if multiple developers are collaborating on the
same feature branch, always coordinate and communicate with your teammates before force pushing, as it can
overwrite others' work. Avoid force pushing to
dev
, and never force push tomain
.
- Use descriptive names (e.g.,
- Pull Requests: When ready, open a pull request (PR) against the
dev
branch.- Ensure the PR description is clear about the changes made.
- Request reviews from team members.
- Once approved, we merge the PR into
dev
.
- Releases: When ready to deploy, merge
dev
intomain
.- We do not use squash merging for releases, instead, we use regular merging to preserve commit history.
- After every release, we rebase the
dev
branch frommain
to keep it up to date without introducing merge commits. - We may squash merge features into
dev
to keep the history clean.
- Hotfixes: For urgent fixes, create a hotfix branch from
main
, apply the fix, and merge it back into bothmain
anddev
. Hotfix branches should be named likehotfix/fix-issue
.
The following commands are used to generate and apply migrations to the postgreSQL database. Here you can find an in-depth guide on how to use Prisma Migrate.
###############################################
# Generate and apply migrations to conveniat27.cevi.tools
###############################################
export DB_PASSWORD= # dev deployment database password
export CHAT_DATABASE_URL="postgres://conveniat27:$DB_PASSWORD@db.conveniat27.cevi.tools:443/conveniat27"
# check status (this will show the current migration status)
npx prisma migrate diff --from-url $CHAT_DATABASE_URL --to-schema-datamodel prisma/schema.prisma
# create a new migration
npx prisma migrate dev --schema prisma/schema.prisma
#############################################
# Apply migrations to conveniat27.ch
#############################################
export DB_PASSWORD= # prod deployment database password
export CHAT_DATABASE_URL="postgres://conveniat27:$DB_PASSWORD@conveniat27.ch:443/conveniat27"
npx prisma migrate deploy --schema prisma/schema.prisma
This project is licensed under the MIT License — see the LICENSE
file for details.