Law Firm Template
Complete documentation for installation, configuration, and customization.
Overview
This is a professional, production-ready law firm website template built with the modern Next.js App Router. It ships with a fully-featured admin dashboard that lets you manage every piece of content — from attorney profiles and blog posts to site settings and SEO metadata — without ever touching code.
Public Website
Home, About Us, Blogs, Services, and dynamic Practice Area pages.
Admin Dashboard
Secure, role-based admin panel with full CRUD for all content sections.
Cloudinary Images
Automatic image upload, CDN delivery, and cleanup on delete.
Rich Text Editor
SunEditor WYSIWYG for blog posts and policy content.
Drag & Drop
Reorder testimonials with drag-and-drop using DnD Kit.
SEO Ready
Configurable metadata, Open Graph image, and keywords per-site.
Data Tables
TanStack Table with search, pagination, sorting, and status toggle.
Dark / Light Mode
next-themes integration for user-preferred color scheme.
Requirements
| Requirement | Minimum Version | Notes |
|---|---|---|
| Node.js | 18.17+ | LTS recommended |
| npm / pnpm / yarn | Any recent version | pnpm recommended for speed |
| MongoDB | 6.0+ | Atlas free tier is sufficient |
| Cloudinary Account | Free tier | Required for all image uploads |
Quick Start
-
Install dependencies
npm install # or pnpm install -
Configure environment variables
Create a
.envfile at the project root. See Environment Variables for all required keys. -
Create the first admin user
Insert a user document into your MongoDB
userscollection with a bcrypt-hashed password:// In mongosh or MongoDB Compass: db.users.insertOne({ name: "Admin", email: "admin@yourdomain.com", password: "<bcrypt-hashed-password>", role: "admin", createdAt: new Date(), updatedAt: new Date() }) -
Configure Cloudinary
Start the dev server, log in at
/admin/login, then go to Settings → Cloudinary and enter your credentials. -
Start the development server
npm run devSite:
http://localhost:3000| Admin:http://localhost:3000/admin/login
Available Scripts
| Command | Description |
|---|---|
| npm run dev | Start development server with Turbopack |
| npm run build | Build for production (Turbopack) |
| npm run start | Start production server |
| npm run lint | Run ESLint |
Environment Variables
# ── Application ───────────────────────────────────────────
AUTH_TRUST_HOST="true"
NEXTAUTH_URL="http://localhost:3000"
NEXT_PUBLIC_BASE_URL="http://localhost:3000"
NEXTAUTH_SECRET="your-strong-random-secret"
# ── Database ───────────────────────────────────────────────
MONGODB_URI="mongodb+srv://user:pass@cluster.mongodb.net/dbname?retryWrites=true&w=majority"
| Variable | Required | Description |
|---|---|---|
| AUTH_TRUST_HOST | Yes |
Set to "true" for all environments to trust the
host header
|
| NEXTAUTH_URL | Yes | Full URL of your site. Must match your domain exactly (no trailing slash) |
| NEXT_PUBLIC_BASE_URL | Yes | Same as NEXTAUTH_URL. Exposed to the browser for API calls |
| NEXTAUTH_SECRET | Yes |
Random string for encrypting sessions. Generate with
openssl rand -base64 32
|
| MONGODB_URI | Yes | MongoDB connection string including database name |
.env.
Tech Stack
Project Structure
Public Pages
| Route | Description |
|---|---|
| / | Home page — Banner, About preview, Practice Areas, Attorneys, Testimonials, Blogs, FAQ, Contact |
| /about-us | Full About Us page with team milestones, core values, and author quote |
| /blogs | Paginated blog listing with category and tag filters |
| /blogs/[slug] | Individual blog post with rich text content and share buttons |
| /services | Services section overview page |
| /[slug] | Dynamic practice area detail page (driven by Practices data) |
| /documentation | This documentation page |
status: true appears in
the site navigation with its slug as the URL path.
Admin Dashboard
Access the admin panel at /admin/login. After
successful login you are redirected to
/admin/dashboard.
| Section | Route | Description |
|---|---|---|
| Overview | /admin/dashboard | Stats: total attorneys, blogs, practice areas, FAQs |
| Banner | /admin/dashboard/banner | Hero heading, background image, person image, success rate & cases count |
| Taglines | /admin/dashboard/taglines | Section taglines for Practice Areas, Attorneys, Testimonials, Blog, FAQ |
| Attorneys | /admin/dashboard/attorneys | Attorney profiles with photo, name, and designation. Toggle visibility. |
| Blog Categories | /admin/dashboard/blog-categories | Manage categories used to filter blog posts |
| Blogs | /admin/dashboard/blogs | Full blog management: create, edit, delete, toggle status |
| Practice Areas | /admin/dashboard/practices | Full practice area pages with multiple images and rich descriptions |
| Services | /admin/dashboard/services | Services section content with tagline, descriptions, and images |
| FAQ | /admin/dashboard/faq | Question & answer pairs. Toggle visibility. |
| Testimonials | /admin/dashboard/testimonials | Client testimonials. Drag-and-drop to reorder display sequence. |
| Awards | /admin/dashboard/awards | Award titles and images. Toggle visibility. |
| About Section | /admin/dashboard/about-section | About page content: tagline, description, core values, quote, milestones |
| Settings | /admin/dashboard/settings | General info, Metadata/SEO, Terms & Policy, Cloudinary config |
API — Authentication
Admin API routes require a valid JWT token in the
Authorization header as
Bearer <token>.
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| POST | /api/admin/auth/login |
Admin login. Body: { email, password }. Returns
JWT token.
|
Public |
| GET | /api/admin/auth/me | Get current admin profile | Required |
| PUT | /api/admin/auth/password |
Change admin password. Body:
{ currentPassword, newPassword }
|
Required |
API — Dashboard
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/admin/dashboard | Returns aggregate counts: attorneys, blogs, practices, FAQs |
API — Attorneys
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/admin/attorneys |
List attorneys. Query: page,
limit, search
|
| POST | /api/admin/attorneys |
Create attorney. multipart/form-data:
name, designation,
image (file)
|
| GET | /api/admin/attorneys/[id] | Get single attorney by ID |
| PUT | /api/admin/attorneys/[id] | Update attorney |
| DELETE | /api/admin/attorneys/[id] | Delete attorney and remove image from Cloudinary |
| PUT | /api/admin/attorneys/status/[id] |
Toggle attorney visibility (status boolean)
|
API — Awards
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/admin/awards | List all awards |
| POST | /api/admin/awards |
Create award. multipart/form-data:
title, image (file)
|
| GET | /api/admin/awards/[id] | Get single award |
| PUT | /api/admin/awards/[id] | Update award |
| DELETE | /api/admin/awards/[id] | Delete award |
| PUT | /api/admin/awards/status/[id] | Toggle visibility |
API — Blog Categories
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/admin/blog-category | List all categories |
| POST | /api/admin/blog-category | Create category. Body: { title } |
| PUT | /api/admin/blog-category/[id] | Update category title |
| DELETE | /api/admin/blog-category/[id] | Delete category |
| PUT | /api/admin/blog-category/status/[id] | Toggle category visibility |
API — Blogs
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/admin/blogs |
List blogs. Query: page, limit,
search, category,
tag, status
|
| POST | /api/admin/blogs |
Create blog. multipart/form-data with
image file and all text fields
|
| GET | /api/admin/blogs/[id] | Get single blog by ID |
| PUT | /api/admin/blogs/[id] | Update blog |
| DELETE | /api/admin/blogs/[id] | Delete blog and remove image from Cloudinary |
| GET | /api/admin/blogs/slug/[slug] | Get blog by URL slug |
| PUT | /api/admin/blogs/status/[id] | Toggle blog visibility |
API — About Section
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/admin/about-section | Get full about section content including milestones and core values |
| PUT | /api/admin/about-section |
Update about section. multipart/form-data with
image fields and nested arrays
|
API — FAQ
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/admin/faq | List all FAQs |
| POST | /api/admin/faq | Create FAQ. Body: { question, answer } |
| PUT | /api/admin/faq/[id] | Update FAQ |
| DELETE | /api/admin/faq/[id] | Delete FAQ |
| PUT | /api/admin/faq/status/[id] | Toggle FAQ visibility |
API — Practice Areas
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/admin/practices |
List practice areas. Query: page,
limit, search
|
| POST | /api/admin/practices |
Create practice area. multipart/form-data:
displayImage, firstImage,
expertiseImage (files) + all text fields
|
| GET | /api/admin/practices/[id] | Get single practice area |
| PUT | /api/admin/practices/[id] | Update practice area |
| DELETE | /api/admin/practices/[id] | Delete practice area and all 3 Cloudinary images |
| PUT | /api/admin/practices/status/[id] | Toggle practice area visibility |
API — Services
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/admin/services | Get services section content |
| PUT | /api/admin/services/[id] |
Update services. multipart/form-data with
optional imageOne and imageTwo
|
API — Settings
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/admin/settings/general | Get general settings (company info, logo, social links) |
| PUT | /api/admin/settings/general |
Update general settings. multipart/form-data:
optional logo, favicon,
contactUsImage
|
| GET | /api/admin/settings/metadata | Get SEO metadata |
| PUT | /api/admin/settings/metadata |
Update SEO metadata. multipart/form-data:
optional openGraphImage
|
| GET | /api/admin/settings/terms | Get Terms of Service and Privacy Policy HTML |
| PUT | /api/admin/settings/terms | Update terms and policy rich text |
| GET | /api/admin/settings/cloudinary | Get Cloudinary credentials |
| PUT | /api/admin/settings/cloudinary | Update Cloudinary credentials |
API — Taglines
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/admin/taglines | Get all section taglines |
| PUT | /api/admin/taglines |
Update taglines. Body:
{ practiceAreaTagline, attorneyTagline,
testimonialTagline, blogTagline, faqTagline }
|
API — Testimonials
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/admin/testimonials | List testimonials ordered by order field |
| POST | /api/admin/testimonials |
Create testimonial. multipart/form-data:
name, testimonial,
image (file)
|
| PUT | /api/admin/testimonials/[id] | Update testimonial |
| DELETE | /api/admin/testimonials/[id] | Delete testimonial |
| PUT | /api/admin/testimonials/status/[id] | Toggle visibility |
| PUT | /api/admin/testimonials/sort |
Update display order after drag-and-drop. Body:
[{ id, order }]
|
API — Public Endpoints
These endpoints are unauthenticated and used by the public-facing
frontend. They only return records with status: true.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/attorneys | Get all active attorneys |
| GET | /api/awards | Get all active awards |
| GET | /api/faq | Get all active FAQs |
| GET | /api/practices | Get all active practice areas |
| GET | /api/practices/slug/[slug] | Get a single practice area by URL slug |
| GET | /api/testimonials |
Get active testimonials ordered by order field
|
Database Schema
User
Attorney
Awards
Blog
Blog Category
FAQ
Practice Areas
Services
Settings
Taglines
Testimonials
About Section
Image Management — Cloudinary
All images in this template are stored and served via Cloudinary.
The server stores only the Cloudinary public_id in
MongoDB and constructs full URLs at query time using the configured
secureUrlBase.
Configuration
After starting the dev server, go to Admin → Settings → Cloudinary and fill in:
| Field | Description | Example |
|---|---|---|
| Cloud Name | Your Cloudinary account cloud name | my-cloud |
| API Key | Cloudinary API key | 123456789012345 |
| API Secret | Cloudinary API secret | AbCdEfGhIjKlMnOpQrStUvWxYz |
| Folder | Base folder for all uploads | law-firm |
| Secure URL Base | Full CDN base URL | https://res.cloudinary.com/my-cloud/image/upload |
Folder Structure in Cloudinary
{folder}/
├── attorneys/
├── awards/
├── blog/
├── practices/
└── settings/
Authentication
Admin Login (JWT)
The admin panel uses custom JWT-based authentication. On
POST /api/admin/auth/login a signed JWT is returned and
stored client-side. All admin API routes verify this token via the
authenticate middleware.
Session Management (NextAuth)
Next-Auth v5 wraps the protected dashboard layout group
(private) to manage session persistence across page
navigations.
Creating the First Admin User
There is no sign-up page. You must insert the first admin user directly into MongoDB:
// Using mongosh
use your-database-name
db.users.insertOne({
name: "Admin",
email: "admin@yourdomain.com",
password: "<bcrypt-hash-of-your-password>",
role: "admin",
createdAt: new Date(),
updatedAt: new Date()
})
Generate a bcrypt hash in Node.js:
const bcrypt = require('bcrypt');
const hash = await bcrypt.hash('YourSecurePassword123!', 12);
// paste this as the password value
Customization
Change Brand Colors
Update the color tokens in src/app/globals.css and
extend the Tailwind theme in tailwind.config.js. All
component color references use CSS custom properties for easy
theming.
Change Logo / Favicon
Go to Admin → Settings → General and upload a new logo and favicon image.
Add Practice Areas
Go to Admin → Practice Areas → Create. Each
practice area requires a title, slug (used as the URL path), three
images, several description fields, and an expertise list. Setting
status: true makes it appear in the site navigation
automatically.
Update SEO
Go to Admin → Settings → Metadata to configure the site title, meta description, keywords, and Open Graph image used across all pages.
Modify Navigation Items
Navigation is dynamically generated from Practice Areas with
status: true. To add or remove nav items, activate or
deactivate the corresponding practice area.
Add New Blog Posts
Go to Admin → Blogs → Create. Write content using the built-in SunEditor WYSIWYG editor. Assign a category, reading time, and publish status.
Deployment
Vercel (Recommended)
-
Push to GitHub / GitLab / Bitbucket
Push your repository to any Git provider.
-
Import in Vercel
Go to vercel.com/new, import the repository, and select the Next.js framework preset.
-
Set environment variables
Add all five environment variables in the Vercel project settings under Settings → Environment Variables.
-
Deploy
Vercel will build and deploy automatically on every push to your main branch.
Self-Hosted (Railway, Render, VPS)
# Build and start
npm run build
npm run start
AUTH_TRUST_HOST=true and make sure
NEXTAUTH_URL matches your production domain exactly
(including protocol, no trailing slash).
Production Environment Variables
AUTH_TRUST_HOST=true
NEXTAUTH_URL=https://yourdomain.com
NEXT_PUBLIC_BASE_URL=https://yourdomain.com
NEXTAUTH_SECRET=<strong-random-secret>
MONGODB_URI=<production-mongodb-connection-string>
Support
For questions, bug reports, or customization requests, please reach out via the support channel associated with your purchase.
-
View this documentation online at
/documentation -
Refer to
public/sample-data/for example data structures -
Check the
README.mdfor a quick reference in markdown format