Construction Pro
A full-stack, production-ready construction company website and admin dashboard template built with Next.js 16, TypeScript, MongoDB, and Tailwind CSS 4.
Introduction
Construction Pro is a modern, full-stack business website template designed specifically for construction, contracting, and engineering companies. It ships with a polished public-facing website and a comprehensive admin dashboard — all content is managed dynamically through the admin panel without requiring any code changes.
The template is built on Next.js 16 with the App Router, uses MongoDB as the primary database, NextAuth v5 for secure JWT-based authentication, and Cloudinary for cloud media storage. The entire frontend is fully responsive with built-in dark mode support.
Full-Stack Ready
Complete frontend and backend in a single Next.js project. No separate API server needed.
Admin Dashboard
Feature-rich CMS with projects, services, banners, settings, and more — all editable without code.
Cloud Media
Cloudinary integration for optimized image delivery via CDN. Auto-cleanup on record deletion.
Secure Authentication
JWT-based sessions with bcrypt password hashing and 30-day token expiry via NextAuth v5.
Dark Mode
System-aware theme with manual toggle. Persisted across sessions using next-themes.
SEO Optimized
Dynamic metadata, Open Graph tags, and keywords managed per-page from the admin panel.
System Requirements
| Requirement | Minimum Version | Notes |
|---|---|---|
| Node.js | 18.0.0 | LTS version recommended |
| npm | 9.0.0 | Included with Node.js 18+ |
| MongoDB | 6.0+ | MongoDB Atlas free tier works |
| Cloudinary Account | Free tier | Required for image uploads |
Quick Start
# 1. Install dependencies
npm install
# 2. Copy and configure environment file
cp .env.example .env.local
# Edit .env.local with your MongoDB URI, NEXTAUTH_SECRET, etc.
# 3. Start the development server
npm run dev
Open http://localhost:3000 for the public website and http://localhost:3000/admin/login for the admin panel.
Installation
Extract or Clone
If downloaded as a ZIP, extract to your project directory. Otherwise clone the repository.
Install Dependencies
Run npm install in the project root to install all packages.
Configure Environment
Copy .env.example to .env.local and fill in all required values. See the Environment Variables section for details.
Set Up MongoDB
Create a free MongoDB Atlas cluster or run a local MongoDB instance. Copy the connection URI into MONGODB_URI.
Start the Server
Run npm run dev — the default admin account is created automatically on first connection.
First Login & Cloudinary
Log in at /admin/login using the default credentials, then go to Settings → Cloudinary to configure your image storage.
Setting Up MongoDB Atlas
- Create a free account at mongodb.com/atlas
- Create a new cluster — the free M0 tier is sufficient
- Go to Database Access and add a user with read/write privileges
- Go to Network Access and whitelist your IP (use
0.0.0.0/0for development) - Click Connect → Drivers and copy the connection string
- Replace
<user>,<password>, and<dbname>in the URI
Setting Up Cloudinary
- Create a free account at cloudinary.com
- Find your Cloud Name, API Key, and API Secret in the Cloudinary Dashboard
- After your first admin login, navigate to Settings → Cloudinary and enter these credentials
Environment Variables
Create a .env.local file in the project root using .env.example as a template.
| Variable | Required | Description | Example |
|---|---|---|---|
AUTH_TRUST_HOST |
required | Trust proxy host headers for deployment | true |
NEXTAUTH_URL |
required | Full URL of your deployment | https://yourdomain.com |
NEXTAUTH_SECRET |
required | Secret key for signing JWT tokens — minimum 32 characters | a-32-char-random-string |
NEXT_PUBLIC_BASE_URL |
required | Public base URL used by the frontend for API requests | https://yourdomain.com |
MONGODB_URI |
required | MongoDB connection string (Atlas or local) | mongodb+srv://user:pass@cluster.net/db |
Generating a Secure Secret
# Using Node.js
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
# Using OpenSSL
openssl rand -hex 32
Example .env.local
AUTH_TRUST_HOST=true
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=your-32-character-minimum-secret-key-here
NEXT_PUBLIC_BASE_URL=http://localhost:3000
MONGODB_URI=mongodb+srv://admin:password@cluster.mongodb.net/construction
Default Admin Credentials
On first startup, a default admin account is automatically created if no admin user exists in the database.
| Field | Value |
|---|---|
admin@example.com | |
| Password | ChangeMe123! |
| Role | admin |
Project Structure
The project follows Next.js 16 App Router conventions with route groups for logical separation of public, private, and auth pages.
template-construction/
├── app/ # Next.js App Router
│ ├── (auth)/ # Authentication route group
│ │ └── admin/login/ # Admin login page
│ ├── (public)/ # Public website route group
│ │ ├── layout.tsx # Public layout (navbar + footer)
│ │ ├── page.tsx # Home page
│ │ ├── contact/ # Contact form page
│ │ ├── services/ # Services listing page
│ │ └── projects/
│ │ ├── page.tsx # Projects listing
│ │ └── [slug]/page.tsx # Project detail page
│ ├── (private)/ # Protected admin route group
│ │ ├── layout.tsx # Admin layout (sidebar + header)
│ │ └── admin/dashboard/ # Admin dashboard pages
│ │ ├── page.tsx # Dashboard overview
│ │ ├── banner/ # Hero banner management
│ │ ├── projects/ # Projects CRUD
│ │ ├── services/ # Services CRUD
│ │ ├── site-projects/ # Site projects management
│ │ ├── contacts/ # Contact submissions
│ │ ├── subscribe/ # Newsletter subscribers
│ │ ├── settings/ # Settings hub (6 tabs)
│ │ ├── about-showcase/ # About section editor
│ │ ├── our-work/ # Our Work section editor
│ │ ├── our-strength/ # Strengths section editor
│ │ ├── service-banner/ # Service page banner
│ │ └── home-banner/ # Home banner config
│ ├── api/ # API route handlers
│ │ ├── auth/[...nextauth]/ # NextAuth.js handler
│ │ ├── admin/ # Protected admin endpoints
│ │ │ ├── auth/ # Login, profile, password
│ │ │ ├── project/ # Project CRUD + status
│ │ │ ├── service/ # Service CRUD + status
│ │ │ ├── site/ # Site CRUD + sort
│ │ │ ├── banner/ # Banner management
│ │ │ ├── banner-section/ # Banner section management
│ │ │ ├── home-section/ # Home section management
│ │ │ ├── contact-us/ # Contact management
│ │ │ ├── subscribe/ # Subscriber list
│ │ │ ├── file/ # File upload/delete
│ │ │ ├── settings/ # All settings endpoints
│ │ │ └── dashboard/ # Dashboard statistics
│ │ └── public/ # Open API endpoints
│ │ ├── project/ # Public projects
│ │ ├── service/ # Public services
│ │ ├── site/ # Public sites
│ │ ├── banner/ # Public banners
│ │ ├── banner-section/ # Section by slug
│ │ ├── home-section/ # Home section by slug
│ │ ├── contact-us/ # Submit form
│ │ ├── subscribe/ # Newsletter subscription
│ │ └── settings/ # Public settings
│ ├── documentation/ # Documentation viewer route
│ ├── globals.css # Global CSS styles
│ └── layout.tsx # Root layout (providers + fonts)
│
├── components/ # React components
│ ├── ui/ # Base UI (Radix + Tailwind)
│ ├── features/landing/ # Landing page sections
│ ├── shared/ # Header, Footer, etc.
│ ├── custom/ # Domain-specific components
│ └── providers/ # Context providers
│
├── models/ # Mongoose models
│ ├── User.ts
│ ├── Project.ts
│ ├── Service.ts
│ ├── Banner.ts
│ ├── HomeSection.ts
│ ├── Settings.ts
│ ├── Sites.ts
│ ├── Subscribe.ts
│ └── ContactUs.ts
│
├── actions/ # Next.js Server Actions
├── lib/ # Utilities & helpers
│ ├── async-handler.ts # API route wrapper
│ ├── authenticate.ts # JWT utilities
│ ├── mongo-adapter.ts # Pagination helper
│ ├── fileUpload.ts # Cloudinary utilities
│ ├── validation-schema.ts # Zod schemas
│ └── types/ # TypeScript types
│
├── config/
│ ├── database.ts # MongoDB + auto-seeding
│ ├── routes.ts # Route definitions
│ ├── cloudinary.ts # Cloudinary SDK config
│ └── constant.ts # App constants
│
├── hooks/ # Custom React hooks
├── public/docs/ # This documentation file
├── .env.example # Environment template
├── next.config.ts # Next.js config
├── tailwind.config.ts # Tailwind config
└── Dockerfile.dev # Docker dev config
Pages & Routes
Public Pages
| Route | Description |
|---|---|
/ | Home page with all landing sections |
/services | Services grid listing |
/projects | Project portfolio listing |
/projects/[slug] | Individual project detail page |
/contact | Contact form page |
/documentation | Documentation viewer (this page) |
Admin Pages (Authentication Required)
| Route | Description |
|---|---|
/admin/login | Admin authentication |
/admin/dashboard | Statistics overview |
/admin/dashboard/projects | Projects CRUD table |
/admin/dashboard/services | Services CRUD table |
/admin/dashboard/site-projects | Site projects with drag-and-drop reorder |
/admin/dashboard/contacts | Contact form submissions |
/admin/dashboard/subscribe | Newsletter subscribers |
/admin/dashboard/banner | Hero section banners |
/admin/dashboard/about-showcase | About section editor |
/admin/dashboard/our-work | Our Work section editor |
/admin/dashboard/our-strength | Strengths section editor |
/admin/dashboard/service-banner | Service page banner editor |
/admin/dashboard/home-banner | Home banner configuration |
/admin/dashboard/settings | Full settings hub with 6 tabs |
API Reference
All admin endpoints require a valid NextAuth session cookie. Public endpoints require no authentication. All responses follow a consistent JSON structure:
// Success response
{ "success": true, "data": {}, "message": "Optional message" }
// Paginated list response
{
"success": true,
"data": [],
"pagination": { "page": 1, "limit": 10, "total": 50, "hasNext": true, "hasPrev": false }
}
// Error response
{ "success": false, "message": "Error description", "errors": {} }
Authentication
| Method | Endpoint | Description | Body |
|---|---|---|---|
| POST | /api/admin/auth/login |
Admin login — returns JWT token | { email, password } |
| GET | /api/admin/auth/me |
Get current admin profile | — |
| PUT | /api/admin/auth/me |
Update admin display name | { name } |
| PUT | /api/admin/auth/password |
Change admin password | { currentPassword, newPassword } |
Login Response Example:
{
"success": true,
"data": {
"token": "<jwt_token>",
"user": {
"_id": "...",
"name": "Admin",
"email": "admin@example.com",
"role": "admin"
}
}
}
Projects
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/admin/project | List projects — query: ?page=1&limit=10&search=query |
| POST | /api/admin/project | Create project (FormData with 4 images) |
| GET | /api/admin/project/[id] | Get project by MongoDB ID |
| PUT | /api/admin/project/[id] | Update project (FormData) |
| DELETE | /api/admin/project/[id] | Delete project and its Cloudinary images |
| PUT | /api/admin/project/status/[id] | Toggle active/inactive status |
Project FormData Fields
| Field | Type | Required | Description |
|---|---|---|---|
title | string | required | Project title (text-indexed) |
subTitle | string | required | Short subtitle |
description | string | required | Full description — HTML content |
type | string | required | Project type or category |
status | boolean | optional | Visible on site (default: true) |
imageOne | File | required | Primary image |
imageTwo | File | required | Second image |
imageThree | File | required | Third image |
imageFour | File | required | Fourth image |
Services
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/admin/service | List services — ?page=1&limit=10&search=query |
| POST | /api/admin/service | Create service (FormData) |
| GET | /api/admin/service/[id] | Get service by ID |
| PUT | /api/admin/service/[id] | Update service (FormData) |
| DELETE | /api/admin/service/[id] | Delete service and image |
| PUT | /api/admin/service/status/[id] | Toggle status |
Site Projects
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/admin/site | List sites |
| POST | /api/admin/site | Create site (FormData) |
| GET | /api/admin/site/[id] | Get site by ID |
| PUT | /api/admin/site/[id] | Update site (FormData) |
| DELETE | /api/admin/site/[id] | Delete site and image |
| PUT | /api/admin/site/status/[id] | Toggle status |
| PUT | /api/admin/site/sort | Reorder — body: { items: [{ id, position }] } |
Banners & Sections
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/admin/banner | List all banners |
| POST | /api/admin/banner | Create banner |
| GET | /api/admin/banner-section/[slug] | Get banner section by slug |
| PUT | /api/admin/banner-section | Update banner section (FormData) |
| GET | /api/admin/home-section/name/[slug] | Get home section by slug |
| POST | /api/admin/home-section | Create or update home section |
Contacts
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/admin/contact-us | List contact submissions — ?page=1&limit=10 |
| GET | /api/admin/contact-us/[id] | Get single submission |
| PUT | /api/admin/contact-us/[id] | Update read/unread status |
| DELETE | /api/admin/contact-us/[id] | Delete submission |
Subscribers
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/admin/subscribe | List all newsletter subscribers — ?page=1&limit=10 |
File Management
| Method | Endpoint | Description | Body |
|---|---|---|---|
| POST | /api/admin/file |
Upload file to Cloudinary | FormData { file } |
| DELETE | /api/admin/file |
Delete file from Cloudinary | { publicId } |
// Upload response
{
"success": true,
"data": {
"url": "https://res.cloudinary.com/your-cloud/image/upload/v1234/folder/filename.jpg",
"publicId": "folder/filename"
}
}
Settings
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/admin/settings/general | Get general settings |
| PUT | /api/admin/settings/general | Update general settings — includes logo & favicon upload |
| GET | /api/admin/settings/metadata | Get SEO metadata |
| PUT | /api/admin/settings/metadata | Update SEO metadata — includes OG image upload |
| GET | /api/admin/settings/business-hour | Get business hours config |
| PUT | /api/admin/settings/business-hour | Update business hours (7 day entries) |
| GET | /api/admin/settings/cloudinary | Get Cloudinary credentials |
| PUT | /api/admin/settings/cloudinary | Update Cloudinary credentials |
| GET | /api/admin/settings/page-banner | Get per-page banner settings |
| PUT | /api/admin/settings/page-banner | Update per-page banners (FormData) |
| GET | /api/admin/settings/terms | Get terms & privacy policy HTML |
| PUT | /api/admin/settings/terms | Update terms & privacy policy |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/admin/dashboard/stats | Get counts: projects, services, contacts, subscribers |
Public API (No Authentication)
All public endpoints return only active and published records. No authentication header is required.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/public/project | Active projects — ?page=1&limit=9&search=query |
| GET | /api/public/project/[slug] | Single project by slug |
| GET | /api/public/service | All active services |
| GET | /api/public/site | All active site projects |
| GET | /api/public/banner | All active banners |
| GET | /api/public/banner-section/[slug] | Banner section by slug |
| GET | /api/public/home-section/name/[slug] | Home section by slug |
| POST | /api/public/contact-us | Submit contact form — { name, email, phone?, message? } |
| POST | /api/public/subscribe | Newsletter subscription — { email } |
| GET | /api/public/settings | All public-facing settings |
Database Schema
The database uses MongoDB with Mongoose ODM. All models include automatic createdAt and updatedAt timestamps.
User
{
_id: ObjectId,
name: String, // Admin display name
email: String, // Unique — used for login
password: String, // bcrypt hashed (never returned in API responses)
role: "admin" | "user",
createdAt: Date,
updatedAt: Date
}
Project
{
_id: ObjectId,
title: String, // Text-indexed for full-text search
slug: String, // Unique — auto-generated from title
subTitle: String, // Text-indexed
description: String, // HTML content — text-indexed
type: String, // Project category or type
status: Boolean, // true = visible on public site
imageOne: String, // Cloudinary URL
imageTwo: String, // Cloudinary URL
imageThree: String, // Cloudinary URL
imageFour: String, // Cloudinary URL
createdAt: Date,
updatedAt: Date
}
Service
{
_id: ObjectId,
title: String, // Text-indexed
description: String, // HTML content — optional
image: String, // Cloudinary URL
status: Boolean, // true = visible on public site
createdAt: Date,
updatedAt: Date
}
Banner
{
_id: ObjectId,
heading: String,
shortDesc: String,
image: String, // Cloudinary URL
sectionName: "banner" | "section",
position: Number, // Display order — indexed
status: Boolean,
cards: [
{ title: String, image: String, link: String }
],
createdAt: Date,
updatedAt: Date
}
HomeSection
{
_id: ObjectId,
sectionKey: String, // Unique slug (e.g. "about", "our-work", "our-strength")
title: String,
subTitle: String,
sectionImage: String, // Cloudinary URL
orderBy: Number, // Display order — indexed
status: Boolean,
sectionType: String, // Content type hint
images: [String], // Array of Cloudinary URLs
videos: [String],
features: [
{ title: String, description: String, image: String }
],
stats: [
{ value: String, suffix: String, label: String }
],
serviceIds: [ObjectId], // References to Service documents
content: Mixed, // Flexible additional JSON data
createdAt: Date,
updatedAt: Date
}
Settings (Single Document)
{
general: {
companyName, companyDialCode, companyPhone, companyAddress,
logo, // Cloudinary URL
favicon, // Cloudinary URL
supportEmail, ownerName, ownerEmail,
facebook, instagram, twitter, youtube,
homeView, title
},
pageBanner: {
service: { title, description, image }, // Service page hero
project: { title, description, image }, // Projects page hero
contact: { title, description, image } // Contact page hero
},
cloudinary: {
cloudName, apiKey, apiSecret, folderName, secureUrlBase
},
metadata: {
title, applicationName, description,
keywords: [String],
openGraphImage // Cloudinary URL
},
termsPolicy: {
terms: String, // HTML content
policy: String // HTML content
},
businessHours: [ // Exactly 7 entries — one per day
{
dayOfWeek: 0 | 1 | 2 | 3 | 4 | 5 | 6, // 0=Sunday, 6=Saturday
openTime: Number, // Minutes from midnight (540 = 9:00 AM)
closeTime: Number, // Minutes from midnight (1080 = 6:00 PM)
isClosed: Boolean
}
]
}
Site
{
_id: ObjectId,
title: String,
subTitle: String,
image: String, // Cloudinary URL
link: String, // External URL
position: Number, // Sort order — managed via drag-and-drop
status: Boolean,
createdAt: Date,
updatedAt: Date
}
Subscribe
{
_id: ObjectId,
email: String, // Unique, lowercase, trimmed
createdAt: Date,
updatedAt: Date
}
ContactUs
{
_id: ObjectId,
name: String,
email: String, // Lowercase
phone: String, // Optional
message: String, // Optional
status: Boolean, // true = read, false = unread
createdAt: Date,
updatedAt: Date
}
Admin Dashboard Guide
The admin dashboard is accessible at /admin/dashboard after logging in. It provides a complete CMS for managing all website content.
Managing Projects
- Go to Dashboard → Projects
- Click Add New Project to open the create form
- Fill in: Title, Subtitle, Type, Description (rich text editor), and upload 4 images
- Click Save — the URL slug is auto-generated from the title
- Use the Status toggle to show or hide the project on the public site
- Click the Edit icon to modify existing projects
- Click the Delete icon to permanently remove a project — this also deletes its images from Cloudinary
Managing Services
- Go to Dashboard → Services
- Click Add New Service
- Enter a title, optional description (rich text), and upload a featured image
- Toggle Status to control visibility on the public site
Managing Site Projects
- Go to Dashboard → Site Projects
- Add entries with title, subtitle, a thumbnail image, and an external URL
- Drag and drop rows in the table to reorder their display position on the website
- Toggle status per site to control visibility
Contact Submissions
- Go to Dashboard → Contacts
- View all contact form submissions including name, email, phone, and message
- Mark submissions as read or unread to track follow-up status
- Delete processed submissions as needed
Banner Management
- Go to Dashboard → Banner
- Edit the hero section heading, short description, and background image
- Add, edit, or remove CTA cards — each card has a title, image, and link URL
Home Section Editors
| Admin Route | Section | Content You Edit |
|---|---|---|
/about-showcase | About Us | Title, description, image, statistics |
/our-work | Our Work | Title, description, project gallery |
/our-strength | Our Strengths | Title, feature cards (icon + text) |
/service-banner | Service Banner | Service page hero image and text |
/home-banner | Home Banner | Additional home page hero configuration |
Settings Hub
Accessible at /admin/dashboard/settings — organized into six tabs:
General
Company name, phone number (with international dial code), address, support email, owner name and email, social media links (Facebook, Instagram, Twitter, YouTube), logo upload, and favicon upload.
Metadata
SEO page title, application name, meta description, keywords array, and Open Graph image for social media sharing previews.
Business Hours
Configure open and close times for each day of the week (Sunday through Saturday). Mark individual days as permanently closed. Times are stored as minutes from midnight — e.g. 540 = 9:00 AM, 1080 = 6:00 PM.
Cloudinary
Enter your Cloudinary Cloud Name, API Key, API Secret, and upload folder name. All image uploads will use these credentials.
Page Banners
Set a custom hero banner (image, title, and description) independently for the Services page, Projects page, and Contact page.
Terms & Policy
Rich text editor for the Terms of Service and a separate editor for the Privacy Policy. Both support full HTML formatting.
Image Management (Cloudinary)
All images in this template are stored and served via Cloudinary. No files are saved to the server filesystem.
Initial Setup
- Create a free account at cloudinary.com
- After your first admin login, navigate to Settings → Cloudinary
- Enter your Cloud Name, API Key, and API Secret from your Cloudinary Dashboard
- Set a folder name (e.g.,
construction-template) to keep uploads organized - Click Save — all subsequent uploads will use these credentials
How It Works
When any image is uploaded in the admin panel, the file is sent to /api/admin/file. The server uploads it to your Cloudinary account using the stored credentials, then returns a secure CDN URL. Only the URL string is saved in MongoDB. When a record (project, service, etc.) is deleted, the corresponding Cloudinary images are also automatically removed.
Supported Formats
JPEG · JPG · PNG · WebP · SVG
Upload Limits
Maximum 50 MB per file, configured in next.config.ts under serverActions.bodySizeLimit.
Customization Guide
Changing Brand Colors
Update CSS custom properties in app/globals.css:
:root {
--primary: your-hsl-value;
--primary-foreground: your-hsl-value;
}
Or extend the Tailwind theme in tailwind.config.ts:
theme: {
extend: {
colors: {
primary: {
DEFAULT: "#your-color",
foreground: "#your-foreground-color"
}
}
}
}
Changing Fonts
The template uses Poppins for body text and Teko for headings. To change them, edit app/layout.tsx:
import { YourFont } from "next/font/google";
const yourFont = YourFont({
weight: ["400", "600", "700"],
subsets: ["latin"],
variable: "--font-your-font",
});
Adding New Admin Pages
- Create the folder and
page.tsxatapp/(private)/admin/dashboard/your-page/ - Register the route in
config/routes.ts - Add a sidebar navigation link in the admin sidebar component
Adding New Public Pages
- Create
app/(public)/your-page/page.tsx - The page is immediately available at
/your-pageand inherits the public layout (navbar + footer)
API Response Format
All API routes use a consistent response wrapper. To extend the API, follow this pattern:
// lib/async-handler.ts wraps all API routes
// Return a consistent response shape:
return NextResponse.json({
success: true,
data: yourData,
message: "Optional success message"
});
// For errors:
return NextResponse.json(
{ success: false, message: "Descriptive error" },
{ status: 400 }
);
Deployment
Vercel (Recommended)
Vercel is the recommended deployment platform for Next.js applications.
- Push the project to GitHub, GitLab, or Bitbucket
- Import the repository at vercel.com/new
- Add all environment variables in the Vercel project settings
- Set
AUTH_TRUST_HOST=truein Vercel environment settings - Deploy — Vercel handles builds, HTTPS, and CDN automatically
# Optional: deploy via Vercel CLI
npm i -g vercel
vercel --prod
Docker
A Dockerfile.dev is included for containerized development:
docker build -f Dockerfile.dev -t construction-template .
docker run -p 3000:3000 --env-file .env.local construction-template
For production, create a multi-stage Dockerfile:
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:18-alpine AS runner
WORKDIR /app
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["npm", "start"]
Traditional VPS / Server
# Install Node.js 18+
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs
# Build
npm ci
npm run build
# Run with PM2
npm i -g pm2
pm2 start npm --name "construction" -- start
pm2 save
pm2 startup
Nginx reverse proxy configuration:
server {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_cache_bypass $http_upgrade;
}
}
sudo certbot --nginx -d yourdomain.com
Build Commands
| Command | Description |
|---|---|
npm run dev | Start development server with Turbopack (fast HMR) |
npm run build | Build the application for production |
npm run start | Start the production server |
npm run lint | Run ESLint for code quality checks |
Changelog
v1.0.0 — Initial Release
- Full-stack construction website template with admin dashboard
- Public pages: Home, Services, Projects (with detail), Contact
- MongoDB connection pooling and automatic default admin seeding
- NextAuth v5 JWT authentication with 30-day sessions
- Cloudinary media management with auto-cleanup on record deletion
- Fully responsive design with dark mode support
- Dynamic SEO metadata management from admin panel
- Business hours configuration (per day, open/close times)
- Drag-and-drop site project reordering with dnd-kit
- Rich text editor (SunEditor) for descriptions and legal content
- Zod validation on all API endpoints (client and server)
- TanStack React Table with search, sort, and pagination for all data grids
Support
For questions, bugs, or customization assistance:
- Documentation: Visit
/documentationin the app or openpublic/docs/documentation.html - Issues: Report via the purchase platform's comments or support system
Before Contacting Support
- Verify all environment variables in
.env.localare correctly set - Confirm your MongoDB connection string is valid and the IP address is whitelisted in Atlas
- Check that Cloudinary credentials are saved under Settings → Cloudinary in the admin panel
- Ensure Node.js version is 18.0.0 or higher:
node --version
message field that describes the exact issue.
Built with Next.js 16 · TypeScript · MongoDB · Tailwind CSS 4 · v1.0.0