12 KiB
ADVdoors.ru Modern Site Rebuild
Rebuild advdoors.ru as a modern, self-hosted product catalog with cart/order functionality and an intuitive admin panel, using a Turborepo + pnpm monorepo with Next.js + Payload CMS + PostgreSQL + MinIO, deployed with Docker Compose. Includes a scraper app for migrating data from the old site.
Current State
The existing advdoors.ru is a dated product catalog for Finnish doors (KASKI, SWEDOOR/JELD-WEN, ALAVUS, Abloy). It has ~200-500 products across categories (exterior doors, interior doors, accessories), with product pages containing images, prices, discounts, technical specs, and availability. The repo is a fresh start.
Stack
Core: Turborepo + pnpm monorepo
The project uses a Turborepo monorepo managed by pnpm workspaces. This keeps the main web app, the migration scraper, and shared packages in one repo with fast, cached builds.
- Turborepo -- orchestrates builds, dev, lint, typecheck across apps/packages with caching
- pnpm -- fast, disk-efficient package manager with strict dependency isolation
Apps and Packages
apps/web-- Next.js 15 + Payload CMS 3 (storefront + admin in one app)apps/scraper-- Node.js CLI tool to crawl advdoors.ru and import data into the new sitepackages/shared-- Shared TypeScript types, constants (brands, availability statuses, etc.)packages/tsconfig-- Shared TypeScript configurationspackages/eslint-config-- Shared ESLint configurations
Web App: Next.js 15 + Payload CMS 3
Payload CMS v3 embeds directly inside a Next.js application -- one codebase, one deployment:
- Payload CMS 3 provides the admin panel (
/admin), database ORM, authentication, media management, and API layer -- all as a Next.js plugin, not a separate service - Next.js 15 (App Router) provides SSR/SSG for SEO, React Server Components for performance, and the storefront UI
- PostgreSQL 16 as the database (Payload's Drizzle adapter)
- MinIO as S3-compatible object storage for product images and media from day one
- Tailwind CSS v4 for styling the storefront
- TypeScript throughout
Scraper App
A standalone Node.js CLI tool in apps/scraper for one-time data migration:
- Crawls advdoors.ru catalog pages, following pagination
- Extracts product data: name, article number, price, discount, images, specs, category, availability
- Downloads product images and uploads them to MinIO
- Creates products/categories in Payload CMS via its REST or Local API
- Imports shared types from
packages/sharedto ensure data consistency - Uses Cheerio for HTML parsing, got/undici for HTTP
Why this stack over alternatives
- Strapi + Next.js -- Two separate apps to deploy/maintain; Payload v3 integrates into Next.js directly
- Medusa.js -- Full e-commerce engine -- overkill when no payment processing is needed
- WordPress + WooCommerce -- Not modern, poor DX, PHP stack
- Directus + Nuxt -- Good option, but two apps; Payload's admin UI is more polished for this use case
Deployment: Docker Compose (self-hosted)
graph LR
subgraph docker [Docker Compose]
Nginx["Nginx (reverse proxy + SSL)"]
App["Next.js + Payload CMS"]
DB["PostgreSQL 16"]
MinIO["MinIO (S3 storage)"]
end
Browser --> Nginx
Nginx --> App
Nginx -->|"static media"| MinIO
App --> DB
App -->|"upload/fetch media"| MinIO
- Nginx -- reverse proxy, SSL termination (Let's Encrypt), static asset caching, proxies
/mediato MinIO - App -- single Next.js container serving both storefront and
/adminpanel - PostgreSQL 16 -- product data, orders, users, media metadata
- MinIO -- S3-compatible object storage for all product images and media uploads; Payload's S3 storage adapter connects directly to it; Nginx serves media publicly with caching headers
Data Model (Payload Collections)
Products
name(text, localized if needed later)slug(auto-generated from name)sku/articleNumber(text, e.g., "73146")category(relationship to Categories)brand(select: KASKI, ALAVUS, SWEDOOR/JELD-WEN, etc.)images(array of uploads, with gallery support)price(number, rubles)discountPrice(number, optional)availability(select: in-stock / made-to-order / coming-soon)shortDescription(text)technicalSpecs(rich text -- for detailed specs like on the current site)options(array of {name, priceModifier, description} -- for paid customizations)relatedProducts(relationship, self-referencing)seoMeta(meta title, description, OG image)
Categories
name(text)slug(auto)parent(self-relationship for hierarchy: "Exterior Doors" > "With Glass")description(rich text)image(upload)
Orders
orderNumber(auto-incrementing)items(array of {product, quantity, priceAtOrder})customer(group: name, phone, email, comment)status(select: new / in-progress / completed / cancelled)createdAt(auto)- Triggers email notification to admin on new order
Pages (for static content)
title,slug,content(rich text),seoMeta- Used for: About, Delivery, Installation, Warranty, Contacts
SiteSettings (global)
phone,whatsapp,telegram,emailaddressworkingHoursfooterTextsocialLinks
Media (Payload uploads with S3/MinIO)
- Payload's
@payloadcms/storage-s3adapter stores all uploads in MinIO - Auto image resizing/optimization via Sharp
- WebP generation for thumbnails and display sizes
- MinIO bucket:
advdoors-media, public-read policy for product images
Storefront Pages
Public pages (Next.js App Router)
/-- Hero section, featured products, brand highlights, "why choose Finnish doors" section/catalog-- All products with sidebar filters (category, brand, price range, availability), search, pagination/catalog/[categorySlug]-- Category-filtered view/product/[slug]-- Product detail: image gallery (lightbox), specs, price, options, "add to cart", related products/cart-- Cart contents, quantity adjustment, order form (name, phone, email, comment), submit order/[pageSlug]-- Dynamic content pages (about, delivery, installation, warranty, contacts)
Admin panel (Payload CMS, /admin)
Out-of-the-box from Payload:
- Dashboard with order count, recent orders
- Product CRUD with image upload, drag-and-drop reordering
- Category management with tree view
- Order management with status updates
- Page content editor (rich text with embedded images)
- Site settings editor
- User management (admin accounts)
The admin UI is modern, responsive, and intuitive enough for a non-technical user -- it resembles familiar CMS interfaces.
Key Features
For customers
- Fast, responsive, mobile-first design
- Product search (full-text via PostgreSQL)
- Category filtering + price range + brand filters
- Image galleries with zoom
- Shopping cart (persisted in localStorage, synced on order submit)
- Order form with phone/email (no payment -- order goes to admin)
- WhatsApp / Telegram click-to-chat buttons
- SEO: server-rendered pages, meta tags, structured data (JSON-LD Product schema), sitemap.xml
For the admin (father)
- Simple login at
/admin - Add/edit products with drag-and-drop image upload
- Set prices and discounts
- Manage categories
- View and manage incoming orders (with email notifications)
- Edit content pages (about, delivery, etc.)
- Update contact info and site settings
Technical
- Turborepo cached builds (
pnpm turbo build-- only rebuilds what changed) - Docker Compose one-command deployment (
docker compose up -d) - Automatic SSL via Let's Encrypt (Nginx + certbot)
- MinIO for all media storage (S3-compatible, self-hosted, with web console at
:9001) - Database backups (pg_dump cron job in Docker)
- Image optimization (Sharp, WebP auto-conversion)
- Rate limiting on order submission
- CSRF protection
Project Structure (Turborepo + pnpm)
advdoors/
apps/
web/ # Next.js 15 + Payload CMS 3
src/
app/ # Next.js App Router
(frontend)/ # Route group for public pages
page.tsx # Home
catalog/
product/
cart/
[slug]/ # Dynamic content pages
(payload)/ # Payload admin routes (auto-generated)
collections/ # Payload collection definitions
Products.ts
Categories.ts
Orders.ts
Pages.ts
Media.ts
globals/ # Payload globals
SiteSettings.ts
components/ # React components (storefront UI)
lib/ # Utilities, cart logic, API helpers
payload.config.ts # Payload CMS configuration
public/ # Static assets (favicon, robots.txt)
Dockerfile
next.config.ts
tailwind.config.ts
package.json
scraper/ # Migration scraper CLI
src/
index.ts # Entry point
crawl.ts # Page crawler (pagination, link following)
extract.ts # HTML parser (Cheerio) for product data
import.ts # Payload API client for creating records
download-media.ts # Image downloader + MinIO uploader
package.json
packages/
shared/ # Shared types, constants, brand lists
src/
types.ts # Product, Category, Order types
constants.ts # Brands, availability statuses
package.json
tsconfig/ # Shared tsconfig bases
base.json
nextjs.json
node.json
package.json
eslint-config/ # Shared ESLint configs
base.js
next.js
package.json
docker/
docker-compose.yml # PostgreSQL + MinIO + App + Nginx
docker-compose.dev.yml # Dev overrides (hot reload, exposed ports)
nginx.conf
.env.example
turbo.json # Turborepo pipeline config
pnpm-workspace.yaml # pnpm workspace definition
package.json # Root package.json (scripts: dev, build, lint)
.gitignore
Implementation Phases
Phase 1: Foundation (monorepo + infrastructure)
- Initialize Turborepo + pnpm workspace with
apps/web,apps/scraper,packages/* - Scaffold Next.js 15 + Payload CMS 3 in
apps/web - Define data model: collections (Products, Categories, Orders, Pages, Media) and globals (SiteSettings)
- Docker Compose: PostgreSQL 16 + MinIO + App + Nginx
- Configure Payload S3 storage adapter pointing to MinIO
- Shared types in
packages/shared - Basic admin panel working, can create products and upload images to MinIO
Phase 2: Storefront
- Home page design and implementation
- Catalog page with sidebar filters (category, brand, price range) and pagination
- Product detail page with image gallery, specs, pricing, options
- Content pages (about, delivery, installation, warranty, contacts)
- Responsive mobile-first design with Tailwind CSS v4
Phase 3: Cart and Orders
- Shopping cart (client-side state + UI)
- Order form and submission
- Order management in admin panel
- Email notifications on new orders
Phase 4: Scraper / Data Migration
- Build scraper in
apps/scraperusing Cheerio + undici - Crawl advdoors.ru: all catalog pages, follow pagination, extract category structure
- Extract per-product: name, SKU, prices, images, specs, availability, options
- Download images and upload to MinIO
- Import all data into Payload CMS via Local API
- Validate imported data against the live site
Phase 5: Polish and Deploy
- SEO (meta tags, structured data, sitemap)
- Image optimization pipeline
- Performance optimization (caching, ISR)
- Nginx config with SSL
- Database backup strategy
- Production deployment
Open Questions / Future Considerations
- Multi-language: Currently Russian only. Payload supports localization if needed later.
- Analytics: Yandex.Metrika integration (simple script tag).
- CDN: Not needed initially for a Russian-focused site, but can add later.
- MinIO replication: Single-node MinIO is fine to start; can add erasure coding or replication later.
- CI/CD: Turborepo's remote caching could be added with a self-hosted cache server if build times grow.