Production website and small admin CMS for Regent Technologies, an industrial blade sharpening and tooling support company in Moratuwa, Sri Lanka.
The public site covers services, products, industries, FAQ, contact, legal pages, and an About page. The private admin area lives only under /hidden-admin and manages products, services, FAQ, and the admin profile. Public pages can render without a database by using seeded fallback content; write actions and admin pages require production services.
- Next.js App Router with Server Components and Metadata API
- Better Auth for admin email/password authentication
- Drizzle ORM with Neon Postgres
- Cloudflare R2 presigned uploads for product and service images
- Resend for password reset and contact emails
- Cloudflare Turnstile for contact form protection
- Google Analytics 4 through
@next/third-parties/google - Dynamic sitemap, robots rules, canonical metadata, Open Graph/Twitter metadata, and JSON-LD structured data
npm install
npm run devOpen http://localhost:3000.
Copy .env.example to .env.local and fill only the services you are testing locally. Public pages can build without a database, but admin actions require DATABASE_URL.
For local admin testing, point DATABASE_URL to a local Postgres database, run migrations, then seed the admin account. For production, replace the same DATABASE_URL value with Neon.
Core production:
NEXT_PUBLIC_SITE_URL="https://www.regenttech.lk"
NEXT_PUBLIC_GA_MEASUREMENT_ID="optional-public-ga4-measurement-id"
DATABASE_URL="postgresql://..."
BETTER_AUTH_URL="https://www.regenttech.lk"
BETTER_AUTH_SECRET="generate-a-32-byte-or-longer-secret"Local Postgres example:
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/regent"
BETTER_AUTH_URL="http://localhost:3000"
BETTER_AUTH_SECRET="use-a-local-32-byte-or-longer-secret"Email:
RESEND_API_KEY="..."
RESEND_FROM_EMAIL="Regent Technologies <noreply@your-domain.com>"Turnstile:
NEXT_PUBLIC_TURNSTILE_SITE_KEY="..."
TURNSTILE_SECRET_KEY="..."Use both keys from the same Cloudflare Turnstile widget and add the production domain in Cloudflare. For local development, leave both Turnstile values empty to bypass the challenge. Do not set only TURNSTILE_SECRET_KEY; the server will reject submissions because the browser cannot produce a token without NEXT_PUBLIC_TURNSTILE_SITE_KEY.
Cloudflare R2:
R2_ACCOUNT_ID="..."
R2_ACCESS_KEY_ID="..."
R2_SECRET_ACCESS_KEY="..."
R2_BUCKET="..."
R2_PUBLIC_BASE_URL="https://cdn.example.com"First admin seed only:
ADMIN_EMAIL="..."
ADMIN_INITIAL_PASSWORD="..."Do not commit real admin credentials or filled env files.
- Page titles use the pattern
Page Title - Regent Technologies. - Public pages have canonical URLs, descriptions, Open Graph, and Twitter card metadata.
- Search and paginated product result URLs are noindexed while canonicalizing to
/products. - Product detail pages include Product JSON-LD and breadcrumb JSON-LD; the FAQ page includes FAQPage and breadcrumb JSON-LD; the root layout includes LocalBusiness and WebSite JSON-LD.
- Product and industry listing pages include ItemList JSON-LD that matches the visible page content.
/sitemap.xmlincludes public static pages, industry detail pages, published product detail pages, and crawlable image sitemap entries for key page/product images./robots.txtallows public content and blocks/hidden-admin/and/api/.- Google Analytics loads only on public routes.
/hidden-admin, password reset, and dashboard routes do not load the Google tag. - Legal pages are noindexed because they are required trust content, not search landing pages, and they are intentionally left out of the sitemap.
Generate a migration after schema changes:
npm run db:generateApply migrations:
npm run db:migrateSeed the initial admin, products, services, and FAQ:
npm run db:seedThe seed script is idempotent and reads the admin email/password from env. Sign-up is disabled during normal runtime.
- Login:
/hidden-admin - Dashboard:
/hidden-admin/dashboard - Products:
/hidden-admin/dashboard/products - Services:
/hidden-admin/dashboard/services - FAQ:
/hidden-admin/dashboard/faqs - Profile:
/hidden-admin/dashboard/profile
Product fields:
nameanddescriptionare required.slug,metaTitle, andmetaDescriptionauto-fill when left blank.- Up to 3 images are supported.
- R2 uploads are available when R2 env vars are configured.
- Admin search and pagination run client-side from server-rendered data, so records remain searchable without exposing a public API.
Service fields:
title,description,image,cta,modalIntro,details, andbestForare required.slugauto-fills from the title when left blank.- Service cards power both
/servicesand the home page service section. - Service details are entered one per line and render in the public service modal.
FAQ fields:
questionandanswerare required.- FAQ records can be searched, sorted by
sortOrder, published/unpublished, edited, and deleted from the custom admin UI.
Admin UX:
- Product, service, and FAQ create/edit/delete flows use custom modal UI, not native browser confirmation dialogs.
- Validation and duplicate-slug errors return inline messages in the modal instead of raw app error screens.
- Data-driven public and admin routes include route-level loading skeletons that match the surrounding layout.
- Add all production env vars in Vercel.
- Run
npm run db:migrateagainst Neon. - Run
npm run db:seedonce with admin seed env vars present. - Remove seed-only env vars from Vercel after the admin account exists.
- Deploy and verify
/,/products,/industries,/contact,/faq, and/hidden-admin. - Submit both the contact form and a product inquiry once with production Turnstile keys enabled.
- Check admin product, service, and FAQ create/edit/delete flows after deployment.
Before release, check these routes on desktop and mobile widths:
//about/services/products/products/precision-blade-sharpening/industries/industries/woodworking-industry/contact/faq/hidden-admin/hidden-admin/dashboard/hidden-admin/dashboard/products/hidden-admin/dashboard/services/hidden-admin/dashboard/faqs
This repository is proprietary software. See LICENSE.md.
npm run lint
npm run typecheck
npm run build
npm audit --audit-level=highDo not run npm audit fix --force without reviewing the resulting dependency graph. This project uses narrow package overrides for vulnerable transitive packages when the direct dependencies are already on their latest compatible versions.