- HeroSection: 3-col layout (copy | concentric circles+dashboard | display headline) - 'use client' + framer-motion entrance animations (slide in from sides, scale centre) - DashboardPreview inline component (compact portal mockup) - Two floating stat mini-cards (Avg Tax Saved, Response Time) - Mobile: stacked layout, right headline column hidden, H1 in left column - ContainerScroll: simplified — removed 72rem scroll container and scroll transforms; now plain layout wrapper with CSS fadeInUp entrance - Header: logo size increased h-10 → h-13 (40px → 52px) - fix: escape apostrophes in ProcessSection, SolutionSection, TestimonialsSection - fix: remove unused customSize param from SpotlightCard - docs: update CONTEXT_HANDOVER.md with session 4 changes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
40 KiB
40 KiB
Axil Accountants — Implementation Plan
Based on:
Concept.mdv1.0 Stack: Next.js 15 · TypeScript · Tailwind CSS v4 · Payload CMS 3 · PostgreSQL (Neon) · Vercel Each feature is isolated and can be developed, tested, and reviewed independently.
Legend
[ ]— Not started[x]— Complete[~]— In progress[!]— Blocked / needs decision
Feature 1 — Project Setup & Repository
- Scaffold Next.js 16.1.6 project with App Router and TypeScript (
create-next-app) - Configure TypeScript (
tsconfig.json): strict mode, path aliases (@/*) - Install and configure Tailwind CSS v4
- Install and configure ESLint 9 (Next.js preset)
- Install and configure Prettier with Tailwind class sorting plugin
- Set up Husky 9 + lint-staged 16 (pre-commit: lint + format)
- Define
.env.localstructure and.env.exampletemplate - Create
src/directory structure (app, components/ui/layout/sections/three/cms, lib, hooks, types, payload) - Initialise Git repository, create
mainanddevelopbranches - Docker: multi-stage Dockerfile (dev/build/production) + docker-compose.yml (app + PostgreSQL 17)
Create Vercel project— N/A: self-hosted on UbuntuConfigure Vercel environment variables— N/A:.env.localon server
Feature 2 — Database & Infrastructure
- Docker PostgreSQL 17 — сервис
dbвdocker-compose.yml(user: axil, db: axil) DATABASE_URIпрописан в.env.local→postgresql://axil:axil_dev@db:5432/axil- Verify database connectivity — healthcheck в docker-compose (
pg_isready) - Media storage — Payload local disk storage (
/public/media) — без внешних сервисов - Email (Resend) — настроить позже (Feature 19, когда нужны реальные письма)
Feature 3 — Payload CMS — Core Installation ✅
- Install Payload CMS 3 into the Next.js project
- Configure
payload.config.ts(serverURL, secret, db, editor, admin) - Mount Payload API handler at
src/app/(payload)/api/[...slug]/route.ts - Mount admin panel at
src/app/(payload)/admin/[[...segments]]/page.tsx - Add
layout.tsxwithRootLayoutfrom@payloadcms/next/layouts(provides ConfigProvider) - Configure local disk storage for Payload media (
/public/media) - Email adapter — deferred (Feature 19, Resend)
- Verify admin panel loads at
/admin— confirmed ✅ - Install Payload Form Builder plugin (
@payloadcms/plugin-form-builder) — installed, re-enabled in Feature 4 - Downgrade Next.js 16.1.6 → 15.4.11 (peer dep compatibility with Payload 3.77)
- Upgrade Docker Node.js 20 → 22 (undici/tsx compatibility)
Feature 4 — CMS Collections Schema ✅
Define all Payload collections and globals. Each collection = one content type the client manages.
4.1 — Media Collection
Mediacollection с локальным хранилищем (/public/media)- Fields:
alt(text, required),caption(text, optional) - Image sizes:
thumbnail(400×300),card(800×600),hero(1920×1080)
- Fields:
4.2 — Users Collection
Userscollection (extends Payload default)- Fields:
name,email,role(select: admin | editor) - Role-based access control: editors can manage content, not settings
- Fields:
4.3 — Services Collection
Servicescollectiontitle(text, required)slug(slug, auto from title, unique)icon(select: bookkeeping | tax | payroll | vat)tagline(text — short subtitle)description(rich text — full description)whatsIncluded(array of bullet strings)whoItsFor(array:{ audience: select, description: text })howItWorks(array of 3:{ step: number, title: text, description: text })faq(array:{ question: text, answer: rich text })relatedTestimonials(relationship → Testimonials, many)seogroup:{ metaTitle, metaDescription, ogImage → Media }publishedAt(date),status(select: draft | published)
4.4 — Blog Posts Collection
Postscollectiontitle(text, required)slug(slug, unique)author(relationship → Users)category(relationship → Categories)coverImage(upload → Media)excerpt(textarea, 150 chars max)content(rich text — Lexical with full feature set)readingTime(number, auto-calculated on save via hook)publishedAt(date)status(select: draft | published | scheduled)tags(array of text)seogroup:{ metaTitle, metaDescription, ogImage → Media }
4.5 — Categories Collection
Categoriescollectionname(text, required)slug(slug)colour(text — hex, for tag colour)
4.6 — Team Members Collection
TeamMemberscollectionname(text, required)role(text)qualifications(text — e.g. "ACCA, MAAT")bio(textarea)photo(upload → Media)linkedIn(text — URL)order(number — display order)
4.7 — Testimonials Collection
TestimonialscollectionclientName(text, required)businessName(text)businessType(select: sole-trader | limited-company | startup | other)rating(select: 1–5)quote(textarea, required)photo(upload → Media, optional)source(select: google | manual)service(relationship → Services, optional — for filtering)featured(checkbox — show in homepage carousel)publishedAt(date)
4.8 — FAQs Collection
FAQscollection (global FAQ bank)question(text, required)answer(rich text)service(relationship → Services, optional)global(checkbox — show on generic FAQ sections)order(number)
4.9 — Form Builder (Payload Plugin Collections)
- Enable
@payloadcms/plugin-form-builder— provides:Formscollection (drag-and-drop field builder in admin)FormSubmissionscollection (stores all submissions)
- Configure supported field types:
text,email,phone,select,checkbox,textarea - Configure submission handlers:
- Email notification (to: configurable per form)
- Webhook trigger (URL: from webhook registry in Site Settings)
- Redirect URL or confirmation message (per form)
4.10 — Navigation Global
Navigationglobalitems(array):label(text)href(text, optional — for simple links)isDropdown(checkbox)children(array, conditional):{ label, href, icon, description }
4.11 — Footer Global
Footerglobalcolumns(array of 4):heading(text)links(array:{ label, href })
contactInfogroup:{ address, phone, email }socialLinksgroup:{ linkedIn, facebook, instagram }legalLinks(array:{ label, href })copyrightText(text)
4.12 — Site Settings Global
SiteSettingsglobal- Brand:
siteName,tagline,logo → Media,logoDark → Media,favicon → Media - Contact:
address,phone,email,officeHours - Social:
linkedIn,facebook,instagram - Analytics:
googleAnalyticsId,googleTagManagerId,facebookPixelId - Booking:
calendlyUrl,calendlyInline(checkbox) - Chat Bot:
chatBotMode(select: disabled | ai | livechat | leadcapture),chatBotCustomCode(code field — for Tawk.to etc.) - Webhooks Registry:
webhooks(array:{ name, url, active }) - Email:
notificationEmail(default recipient for form submissions) - SEO Defaults:
defaultMetaTitle,defaultMetaDescription,defaultOgImage → Media - Scripts:
headerScripts(code),footerScripts(code) — for third-party embeds
- Brand:
Feature 5 — Design System & Base UI Components ✅
- Configure Tailwind CSS with custom design tokens:
// globals.css (@theme inline) colors: { emerald: { DEFAULT: '#3CC68A', // primary — CTA кнопки, иконки, border dark: '#27A870', // hover / active deeper: '#1A8C5B', // тёмные секции light: '#7DDCB0', // tints mist: '#E8F8F1', // светлые фоны секций }, blue: { DEFAULT: '#1B9AD6', // secondary — заголовки, trust, links dark: '#1480B8', // hover light: '#6CC4E8', // tints mist: '#E8F5FC', // светлые фоны }, charcoal: '#162520', // основной текст muted: '#6B7280', // второстепенный текст bg: '#F5FEFA', // фоновый off-white } borderRadius: { card: '16px', hero: '24px', pill: '999px' } fontFamily: { sans: ['Inter', 'sans-serif'], display: ['Satoshi', 'sans-serif'], mono: ['DM Mono', 'monospace'], } - Set up fonts:
Satoshi(variable font) — Fontshare CDN (<link>in layout.tsx, CSS var reference in @theme)Inter—next/font/google→--font-inter→--font-sansDM Mono—next/font/google→--font-dm-mono→--font-mono
- Create CSS variables in
globals.cssfor all design tokens (enables runtime theming) - Button component (
src/components/ui/Button.tsx):- Variants:
primary(green fill),secondary(outline),ghost(text only) - Sizes:
sm,md,lg - States: hover (scale + colour shift), focus (ring), loading (spinner), disabled
- Optional: trailing arrow icon, leading icon
- Variants:
- Icon system (
src/components/ui/icons/):- Custom SVG line icons for each service (Bookkeeping, Tax, Payroll, VAT)
- Shared icons: ArrowRight, CheckCircle, Star, Menu, X, ChevronDown
- All exported as React components with
sizeandcolorprops
- Heading component —
h1–h4with correct size + weight from design system - Tag/Badge component — pill with colour variant (green, grey, blue)
- StarRating component — 1–5 filled star SVGs
- GlassCard component — frosted glass base with emerald border, hover glow (light + dark variants)
- Section layout wrapper —
max-w-[1440px], horizontal padding - Divider component — subtle emerald gradient horizontal rule
- Spinner component — loading state for forms and async content
Feature 6 — Animation Infrastructure
- Install dependencies:
gsap,@gsap/react,lenis,framer-motion,@react-three/fiber,three - Lenis smooth scroll provider (
src/lib/lenis.tsx):LenisProviderwraps the app in root layout- Integrates with GSAP ticker for synchronized animations
- Disabled on mobile if causing performance issues
- GSAP context provider (
src/lib/gsap.ts):- Registers ScrollTrigger plugin
- Exports
gsapinstance with plugins pre-registered - Custom hook
useGSAPfor component-level animation cleanup
useAnimateOnScrollhook (src/hooks/useAnimateOnScroll.ts):- Accepts
ref,animation type(fadeUp, fadeIn, slideLeft, etc.),delay,stagger - Returns trigger state
- Uses ScrollTrigger under the hood
- Accepts
useCountUphook (src/hooks/useCountUp.ts):- Accepts
target(number),duration,prefix(£,+),suffix(%,+) - Triggers animation when element enters viewport
- Returns current display value (string)
- Accepts
AnimatedTextcomponent (src/components/ui/AnimatedText.tsx):- Splits text into words or lines
- Each word/line slides up on a stagger delay
- GSAP-driven, respects
prefers-reduced-motion
FadeInSectioncomponent (src/components/ui/FadeInSection.tsx):- Wraps any content
- Fades + translates up when enters viewport
- Accepts
delayanddurationprops
- Framer Motion page transition (
src/components/layout/PageTransition.tsx):- Wraps each page with
AnimatePresence - Default: fade + subtle Y translate
- Used in root layout
- Wraps each page with
prefers-reduced-motionutility — all animation components check and skip if enabled
Feature 7 — Header & Navigation
Headercomponent (src/components/layout/Header.tsx):- Fixed/sticky at top,
z-50 - On scroll: applies
backdrop-blur+ white/85% opacity background (Framer Motion) - Transparent when at top of page
- Max-width container, flex layout: logo | nav | CTA
- Fixed/sticky at top,
- Logo component — renders logo image from CMS Site Settings, supports light/dark variant
- Desktop navigation (
src/components/layout/DesktopNav.tsx):- Renders items from CMS Navigation global
- Simple links: hover underline slide-in animation
- Dropdown trigger:
Services ▾— rendersServicesDropdown
ServicesDropdowncomponent:- Opens on hover/focus, closes on blur/outside click
- Grid of service cards: icon + name + short description
- Framer Motion: animate-in (scale + fade from top)
- Header CTA button — "Book a Free Consultation" → opens booking modal
MobileMenucomponent (src/components/layout/MobileMenu.tsx):- Hamburger icon button (animated → X on open)
- Full-screen overlay,
fixed inset-0, dark green tint background - Framer Motion: slide-in from right or fade-in
- Staggered nav item reveal
- Large CTA button at bottom
- Closes on link click or backdrop click
- Fetch navigation data from Payload at build/request time (cached)
Feature 8 — Footer
Footercomponent (src/components/layout/Footer.tsx):- 4-column grid (collapses to 2×2 on tablet, 1 column on mobile)
- Column 1: Logo, tagline, social icon links
- Column 2: Services links
- Column 3: Company links (About, Blog, Contact)
- Column 4: Contact info (address, phone, email) + ICAEW/ACCA badges
- Social icon links (LinkedIn, Facebook, Instagram) — SVG icons, hover colour transition
- ICAEW / ACCA badge images (stored in media, linked from Site Settings)
- Bottom bar: copyright text | Privacy Policy | Cookie Policy
- Data fetched from CMS Footer global + Site Settings global
Feature 9 — 3D Hero Scene (Three.js / R3F)
- Install:
@react-three/fiber,@react-three/drei,three GlobeScenecomponent (src/components/three/GlobeScene.tsx):- Canvas with
@react-three/fiber - Camera position, fog, ambient + directional lighting (soft green tint)
- Canvas with
Globemesh:SphereGeometrywith high segment count- Custom
PointsMaterialshader: dot-matrix surface pattern - Colour: sage green
#6BAF7Don near-white background - Slow auto-rotation around Y axis (
useFrame)
ParticleNetwork:- 150–300 floating particle nodes (reduced on mobile: 60–80)
- Lines connecting nearby particles (
LineSegments) - Nodes float with subtle Perlin noise movement
- Colour: soft mint with 40% opacity
- Mouse parallax interaction:
- Track
mousemoveon hero section - Globe and particle network shift slightly (opposite to mouse direction)
useSpring(React Spring or Framer Motion) for smooth interpolation
- Track
- Device-adaptive performance:
- Detect mobile / low-power device
- Reduce particle count, disable post-processing effects
React.Suspenseboundary with skeleton loader during WebGL init
- Mobile fallback (
src/components/three/GlobeFallback.tsx):- 2D SVG animated globe (CSS animation, no WebGL)
- Shown when WebGL unavailable or
prefers-reduced-motionis set
useWebGLSupporthook — detects WebGL availability, returns boolean
Feature 10 — Home Page: Hero Section
HeroSectioncomponent (src/components/sections/home/HeroSection.tsx)- Full-viewport height (
min-h-screen), flex / grid split layout - Left column:
- Eyebrow tag (e.g. "Trusted by 500+ UK Businesses") — Badge component, fade-in
AnimatedTextH1: "Smart Accounting for Growing British Businesses" — word stagger- Subheadline paragraph — fade-in with delay
- Button group: Primary CTA + Secondary CTA (with arrow icon)
- Trust strip: star rating + "4.9 Google Rating" · "ICAEW Certified" · "500+ Clients Served"
- Right column:
GlobeScene(orGlobeFallbackon mobile/no WebGL) - Mobile layout: stacked, 3D scene below text, reduced height
- Content driven from CMS Pages collection (hero fields)
Feature 11 — Home Page: Pain Points Section
PainPointsSectioncomponent- Dark green background (
#1A2E1F), full-width - Headline + subtext —
AnimatedTextreveal - 3
GlassCardcomponents (frosted glass on dark background):- Card 1: "Missed deadlines & HMRC penalties"
- Card 2: "Confused by tax rules that keep changing"
- Card 3: "Hours wasted on bookkeeping instead of growing"
- ScrollTrigger staggered reveal: cards slide up 100ms apart
- Subtle parallax: background shifts 20px on scroll
Feature 12 — Home Page: Services Overview Section
ServicesSectioncomponent- Section header: headline + subheadline
- 2×2
GlassCardgrid (CSS Grid, responsive) - Each card:
- Custom SVG icon (from icon system)
- Service name (heading)
- Short description (from CMS)
- "Learn More →" link to
/services/[slug] - Hover:
translateY(-4px)+ sage green border glow (box-shadow)
- ScrollTrigger staggered card reveal
- CTA block below grid: "Not sure which service you need? → Let's Talk"
- Data: fetch all Services from Payload
Feature 13 — Home Page: Why Choose Axil Section
WhyAxilSectioncomponent- Section headline: "Why Businesses Choose Axil"
- Stat counter row (4 stats):
500+Businesses Served98%Client Retention Rate£2M+Tax Saved for Our Clients12+Years of Experience- Each uses
useCountUphook, triggers on ScrollTrigger enter - Numbers in
DM Monofont
- 4 USP blocks (icon + heading + description):
- ICAEW & ACCA Qualified
- Fixed Monthly Engagement
- Dedicated Account Manager
- Cloud-Based & Paper-Free
FadeInSectionwrapper per block, staggered
Feature 14 — Home Page: Testimonials Section
TestimonialsSectioncomponent- Auto-scrolling marquee carousel (infinite horizontal scroll, CSS animation)
- Pause on hover
- Two rows scrolling in opposite directions (optional premium touch)
TestimonialCardcomponent:- Client name + business type
StarRating(1–5)- Quote text (truncated at 3 lines, expand on click)
- Optional client avatar (from Media, fallback to initials)
- Google Reviews badge (static badge image or API widget)
- ICAEW / ACCA certification badge display
- CTA below: "Join hundreds of satisfied UK businesses → Book Your Free Consultation"
- Data: fetch featured testimonials from Payload (where
featured = true)
Feature 15 — Home Page: Who We Work With Section
AudienceSectioncomponent- 3 audience cards:
- Sole Traders — illustrated icon, description, "Learn More →"
- Limited Companies — illustrated icon, description, "Learn More →"
- Startups — illustrated icon, description, "Learn More →"
- Illustrated icons: custom SVG per audience type
- Cards:
GlassCard, hover lift effect - FadeIn reveal on scroll
Feature 16 — Home Page: How It Works Section
HowItWorksSectioncomponent- 3-step horizontal layout (flex row on desktop, vertical stack on mobile)
- Each step: numbered circle + title + description
- Animated connecting line:
- SVG path between steps
- GSAP
DrawSVGPluginor strokedashoffsetanimation - Draws left-to-right as section scrolls into view
- Step data driven from CMS (global or hardcoded — TBD with client)
Feature 17 — Home Page: Blog Preview Section
BlogPreviewSectioncomponent- Section headline + "Visit Our Blog →" link
- Fetch 3 most recent published posts from Payload
BlogCardcomponent (reused on blog index):- Cover image (
next/image, lazy-loaded) - Category tag badge
- Title (heading)
- Excerpt (truncated)
- Author name + date + read time
- Hover: image subtle zoom, card lift
- Cover image (
- Grid: 3 columns desktop, 1 column mobile
Feature 18 — Home Page: Final CTA Section
FinalCTASectioncomponent- Full-width, deep forest green background (
#1A2E1For#2E7D52) - Centred content:
- Large headline: "Ready to take the stress out of your finances?"
- Subheadline
- Large primary CTA button (white on green): "Book a Free Consultation"
- Trust reassurance: "★ 4.9/5 on Google · ICAEW Certified · No lock-in contracts"
- Optional: subtle animated background (particle drift or gradient shift)
Feature 19 — Consultation Booking Modal & Form
This is the primary conversion mechanism. Opens from every CTA across the site.
BookingModalcomponent (src/components/BookingModal.tsx):- Global state:
useBookingModal()Zustand store or React Context - Triggered by
openBookingModal()from any component AnimatePresence+ Framer Motion: fade-in backdrop, slide-up modal- Close on backdrop click, Escape key, close button
- Accessible: focus trap,
role="dialog",aria-modal
- Global state:
- Two modes (configurable in CMS Site Settings):
- Mode A — Calendly: renders
<iframe>fromcalendlyUrlsetting - Mode B — Custom Form: renders the consultation form
- Mode A — Calendly: renders
- Consultation form fields:
- Full name (text, required)
- Email address (email, required)
- Phone number (text, optional)
- Business type (select: sole trader / limited company / startup / other)
- What do you need help with? (select: bookkeeping / tax / payroll / VAT / not sure)
- Message (textarea, optional)
- Form validation: client-side (React Hook Form + Zod schema)
- Submit handler (API route
POST /api/contact):- Save to Payload FormSubmissions collection
- Send notification email via Resend to admin email (from Site Settings)
- Trigger webhook if configured (from webhooks registry)
- Return success/error response
- Success state: animated checkmark + "We'll be in touch within 1 business day" message
- Error state: clear error messages per field + general error fallback
Feature 20 — Services Pages
20.1 — Services Overview Page (/services)
- Grid of all 4 service cards (large format)
- Each card links to individual service page
- Intro section: headline + subheadline
- CTA at bottom
20.2 — Individual Service Pages (/services/[slug])
- Dynamic route with
generateStaticParams(ISR) - Fetch service data by slug from Payload at build/revalidate time
- Hero section: service name, bold tagline, illustration/icon, CTA button
- What's Included section: bullet list of deliverables (from CMS
whatsIncluded) - Who It's For section: audience cards filtered to relevant segments
- How It Works section: 3-step process (service-specific, from CMS)
- FAQ section: accordion component
- Fetch FAQs related to this service from Payload
- Animated expand/collapse (Framer Motion
AnimatePresence+ height animation) - JSON-LD
FAQPageschema injected viagenerateMetadata
- Testimonials section: filtered by service (from Payload relationship)
- Final CTA section
generateMetadata: SEO title, description, OG image from CMSseogroup
Feature 21 — About Page (/about)
- Fetch team members, site settings from Payload
- Hero section: large headline + team group photo (from Media)
- Our Story section: rich text from CMS (Lexical renderer)
- Team grid section:
TeamMemberCardcomponent: photo, name, role, qualifications, LinkedIn link- Responsive grid: 3 col desktop, 2 col tablet, 1 col mobile
- Hover: subtle overlay with LinkedIn button
- Data from Payload TeamMembers collection (ordered by
orderfield)
- Core Values section: 4 value blocks (icon + title + description)
- Certifications section: ICAEW + ACCA logo badges with descriptions
- Final CTA section
Feature 22 — Blog System
22.1 — Blog Index Page (/blog)
- Fetch all published posts (ISR, revalidate: 3600s)
- Search input: client-side filter by title/excerpt
- Category filter tabs: "All" + each category — filters post grid
- Blog post card grid (3 columns desktop, 1 mobile) —
BlogCardcomponent - Pagination (10 posts per page, Payload
limit/pagequery) - SEO metadata for blog index
22.2 — Individual Blog Post Page (/blog/[slug])
generateStaticParams+ ISR (revalidate: 3600s)- Fetch post by slug from Payload
- Lexical rich text renderer (
src/lib/lexical-renderer.tsx):- Renders all Lexical nodes: headings, paragraphs, lists, blockquotes, images, code blocks
- Custom component for pull quotes (green left border, large text)
- Syntax highlighting for code blocks (
shikiorprism)
- Article layout: hero image (full-width), title, author + date + read time + category tag
- Table of Contents (auto-generated from H2/H3 headings):
- Sticky sidebar on desktop
- Active heading highlight on scroll (Intersection Observer)
- Social share buttons: copy link, Twitter/X, LinkedIn
- Related posts section: 2–3 posts from same category
generateMetadata: full SEO + Open Graph from CMS seo group
Feature 23 — Contact Page (/contact)
- Hero: "Get in Touch" + brief description
- Contact form (renders a Payload Form via form builder):
- Fields: name, email, phone, subject, message
- Submission saves to Payload FormSubmissions + email notification
- Contact info panel: address, phone, email, office hours
- Google Maps embed (iframe, optional — lazy-loaded)
- Office hours block
Feature 24 — Legal Pages (/privacy-policy, /cookie-policy)
- Static page routes
- Content editable via CMS Pages collection (rich text fields)
- Standard typography layout: table of contents sidebar + main content
- Last updated date (from CMS)
Feature 25 — Chat Bot Module
ChatWidgetcomponent (src/components/chat/ChatWidget.tsx):- Floating button (bottom-right), "Chat with us" label
- Slide-up panel animation (Framer Motion)
- Reads
chatBotModefrom Site Settings global
- Mode: disabled — component renders nothing
- Mode: livechat (Tawk.to or custom):
- Reads
chatBotCustomCodefrom Site Settings - Injects script via
useEffect(no SSR) - Custom button hidden; Tawk.to button shown instead
- Reads
- Mode: ai — AI Chat:
- Pre-chat form: collect visitor name + email
- Chat messages UI: user + bot bubbles, loading dots
- API route
POST /api/chat:- Sends message to OpenAI/Claude with system prompt
- System prompt: company info, services, FAQs — fetched from Payload
- Streams response back (SSE or JSON)
- Escalation: if bot confidence low → "Let me connect you with our team" → email notification
- Conversation log saved to Payload (custom
ChatLogscollection or email)
- Mode: leadcapture — Scripted Bot:
- Step 1: "Hi! What's your name?"
- Step 2: "Great, {name}! What's your email address?"
- Step 3: "Are you a sole trader, limited company, or startup?"
- Step 4: "What's your main concern? (tax / bookkeeping / payroll / other)"
- Step 5: "Thanks! Our team will be in touch shortly."
- On completion: POST lead to
/api/contact→ email + webhook
Feature 26 — SEO & Metadata System
generateMetadatafunction utility (src/lib/metadata.ts):- Accepts CMS seo object + fallback to Site Settings defaults
- Returns Next.js
Metadataobject - Handles: title, description, OG image, canonical URL
- Open Graph image generation (
src/app/og/route.tsx):- Dynamic OG images for blog posts using
@vercel/og - Template: Axil logo + post title + sage green background
- Dynamic OG images for blog posts using
- Structured data helpers (
src/lib/structured-data.ts):LocalBusinessschema (name, address, phone, opening hours)AccountingServiceschemaFAQPageschema (for service pages)BlogPostingschema (for blog articles)- Injected via
<script type="application/ld+json">in page<head>
sitemap.ts(src/app/sitemap.ts):- Fetches all published blog posts + service slugs from Payload
- Returns
MetadataRoute.Sitemaparray - Revalidates on ISR
robots.ts(src/app/robots.ts):- Allow all crawlers
- Point to sitemap
- Block
/admin/*
- Verify all pages have correct
<title>tags in browser devtools
Feature 27 — Analytics, Tracking & Cookie Consent
AnalyticsProvidercomponent (src/components/AnalyticsProvider.tsx):- Reads GA ID, GTM ID, FB Pixel ID from CMS Site Settings
- Conditionally loads scripts only after cookie consent granted
- Google Tag Manager integration:
<Script>in<head>and<noscript>in<body>- Only loads after consent
- Cookie consent banner (
src/components/CookieBanner.tsx):- GDPR / UK GDPR compliant
- Framer Motion: slides up from bottom on first visit
- Buttons: "Accept All" | "Reject Non-Essential" | "Manage Preferences"
- Stores consent in
localStorage - Triggers analytics scripts on accept
useConsenthook — reads/writes consent state, exposeshasConsent(category)- Custom code injection from CMS Site Settings:
headerScripts— injected in<head>via Next.js root layoutfooterScripts— injected before</body>
Feature 28 — Form Builder Frontend Renderer
Allows any Payload-built form to be rendered on the frontend.
FormRenderercomponent (src/components/cms/FormRenderer.tsx):- Accepts
formIdprop (Payload form document ID or slug) - Fetches form schema from Payload API
- Dynamically renders all field types:
text,email,phone,textarea→<input>/<textarea>select→<select>or custom dropdowncheckbox→ styled checkbox
- React Hook Form + Zod validation from field config (required, type)
- Accepts
FormFieldcomponent — renders individual field with label, input, error message- Submit handler:
POST /api/form-submissions- Saves to Payload FormSubmissions
- Triggers email + webhook from form config
- Success / error UI states
- Usage: embed
<FormRenderer formId="..." />anywhere on any CMS-managed page
Feature 29 — Performance Optimisation
- All images use
next/imagewith correctsizesprop andpriorityfor LCP images next/fontfor Inter + DM Mono (eliminates layout shift from font load)- Dynamic imports for heavy components:
GlobeScene—dynamic(() => import(...), { ssr: false })ChatWidget—dynamic(() => import(...), { ssr: false })- Lexical renderer —
dynamicimport
- ISR (Incremental Static Regeneration):
- Service pages:
revalidate = 86400(24h) - Blog posts:
revalidate = 3600(1h) - Homepage:
revalidate = 3600
- Service pages:
- Install
@next/bundle-analyzer, run bundle analysis, eliminate large dependencies - Lazy-load all sections below fold (Intersection Observer or Next.js
loading="lazy") - Verify no unused CSS (Tailwind PurgeCSS is automatic)
- Run Lighthouse on all key pages — target: Performance 95+, SEO 100, Accessibility 95+
- Fix any Lighthouse findings before delivery
Feature 30 — Accessibility Audit & Fixes
- Add
<a href="#main-content">Skip to content</a>as first focusable element - Verify all interactive elements are keyboard-navigable (Tab, Enter, Escape)
- All images have meaningful
alttext (enforced by Payload Media collection) - All form fields have associated
<label>elements - All icon-only buttons have
aria-label - Modals: focus trap,
role="dialog",aria-modal, return focus on close - Navigation dropdowns:
aria-expanded,aria-haspopup - Colour contrast: check all text/background combos meet WCAG AA (4.5:1 body, 3:1 large text)
- Test with screen reader (VoiceOver / NVDA)
- Verify
prefers-reduced-motiondisables all non-essential animations
Feature 31 — Responsive Design QA
- Test every page at:
320px,375px,414px,768px,1024px,1280px,1440px,1920px - Verify 3D scene: WebGL on desktop, SVG fallback on mobile
- Verify hamburger menu: opens, nav links work, closes correctly
- Verify booking modal: fills screen on mobile, scrollable content
- Verify all form fields usable with touch keyboard (no content hidden behind keyboard)
- Verify testimonials marquee / carousel: touch-swipeable on mobile
- Verify blog grid: 1-column on mobile, readable font sizes
- Verify footer: stacks correctly on mobile
Feature 32 — Error Handling & Monitoring
- Sentry setup (
@sentry/nextjs):- Install and configure with
SENTRY_DSN - Client + server + edge error capture
- Source maps upload on build
- Install and configure with
- Custom 404 page (
src/app/not-found.tsx):- Brand-styled, helpful message
- Links back to home + contact
- Custom 500 page (
src/app/error.tsx):- Brand-styled error page
- Reports error to Sentry
- All API routes return proper HTTP status codes + JSON error bodies
- Form submission error states: clear per-field errors + general fallback
- Payload API fetch failures: graceful degradation (show fallback content, not crash)
Feature 33 — Deployment (Ubuntu VPS + Docker)
Stack: Ubuntu server · Docker + Docker Compose · Nginx reverse proxy · Let's Encrypt SSL
33.1 — Server preparation
- Install Docker + Docker Compose on Ubuntu:
curl -fsSL https://get.docker.com | sh sudo usermod -aG docker $USER - Install Nginx + Certbot:
sudo apt install nginx certbot python3-certbot-nginx -y - Clone repository on server:
git clone git@github.com:YOUR_ORG/axil-accountants.git /opt/axil cd /opt/axil && git checkout main - Create production
.env.localon server (manually — never commit):DATABASE_URI=postgresql://axil:STRONG_PASS@db:5432/axilPAYLOAD_SECRET=STRONG_SECRET_32_CHARSNEXT_PUBLIC_SITE_URL=https://axilaccountants.co.uk
33.2 — Docker Compose production override
- Create
docker-compose.prod.yml(production overrides):- App:
NODE_ENV=production,restart: always, no port 3000 exposed externally (only via Nginx) - DB: named volume for persistence,
restart: always - Remove dev-only volume mounts
- App:
33.3 — Nginx reverse proxy
- Create
/etc/nginx/sites-available/axilconfig:server_name axilaccountants.co.uk www.axilaccountants.co.ukproxy_pass http://127.0.0.1:3000- Gzip, proxy headers, timeouts
- Enable site:
sudo ln -s /etc/nginx/sites-available/axil /etc/nginx/sites-enabled/ - Obtain SSL certificate:
sudo certbot --nginx -d axilaccountants.co.uk -d www.axilaccountants.co.uk - Verify auto-renewal:
sudo certbot renew --dry-run - Configure
www→ apex redirect in Nginx (301)
33.4 — Deploy script
- Create
scripts/deploy.shat repo root:#!/bin/bash set -e cd /opt/axil git pull origin main docker compose -f docker-compose.yml -f docker-compose.prod.yml build app docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --no-deps app docker image prune -f echo "Deploy complete: $(date)" - Make executable:
chmod +x scripts/deploy.sh - Test deploy:
./scripts/deploy.sh— verify site responds after restart
33.5 — Verification
- Run production build locally first:
docker compose build && docker compose up— no errors - Verify HTTPS works:
curl -I https://axilaccountants.co.uk - Run Lighthouse on production URL — confirm scores meet targets
- Verify DB volume survives container restart (data not lost)
Feature 34 — Content Seeding & CMS Setup
- Create seed script (
src/payload/seed.ts):- Site Settings: company name, logo, contact details, placeholder social URLs
- 4 Services: Bookkeeping, Tax Returns, Payroll, VAT Returns (full content per service)
- 3 Sample Testimonials (marked as featured)
- 3 FAQs per service
- Blog Categories: "Tax Tips", "Business Advice", "HMRC Updates", "Payroll Guide"
- Navigation: full nav structure from concept
- Footer: all columns with links
- Run seed script against production database
- Verify all seeded content displays correctly on frontend
- Create CMS editor user account for client
Feature 35 — Client Handover & Documentation
- CMS User Guide (
docs/cms-guide.md):- How to log into the admin panel
- How to edit page content (services, hero text, etc.)
- How to write and publish a blog post
- How to add a testimonial
- How to add a team member
- How to create a contact form
- How to set up a webhook on a form
- How to change the chat bot mode
- How to update integration settings (GA, GTM, Calendly URL)
- Handover checklist:
- Client has admin login credentials
- Client has SSH access to server (or handover to sysadmin)
- Client has Resend account access (for email settings)
- DNS properly pointing to Vercel
- Google Analytics connected and receiving data
- Booking form submissions arriving in CMS + email
- Mobile tested on real device (iPhone + Android)
- Lighthouse scores confirmed ≥ 95 Performance
Summary: Feature Dependency Order
Feature 1 (Project Setup)
└── Feature 2 (Database)
└── Feature 3 (Payload CMS Core)
└── Feature 4 (CMS Collections Schema)
├── Feature 5 (Design System) ← parallel from here
├── Feature 6 (Animation Infrastructure)
└── Feature 7 + 8 (Header + Footer)
└── Feature 9 (3D Scene)
└── Feature 10 (Hero Section)
└── Features 11–18 (Home Page Sections)
└── Feature 19 (Booking Modal) ← needed by all CTAs
├── Feature 20 (Services Pages)
├── Feature 21 (About Page)
├── Feature 22 (Blog)
├── Feature 23 (Contact Page)
└── Feature 24 (Legal Pages)
├── Feature 25 (Chat Bot)
├── Feature 26 (SEO)
├── Feature 27 (Analytics)
├── Feature 28 (Form Renderer)
├── Feature 29 (Performance)
├── Feature 30 (Accessibility)
├── Feature 31 (Responsive QA)
├── Feature 32 (Error Handling)
└── Feature 33 (Deployment)
├── Feature 34 (Content Seed)
└── Feature 35 (Handover)
Total features: 35 | Estimated tasks: ~220 Last updated: February 2026