DEV Community

Allen Jones
Allen Jones

Posted on • Originally published at formgrid.dev

How to Build a $15K Web Design Agency Landing Page With Next.js and Formgrid

Most web design agencies charge between $5K and $20K
for a professional landing page. The difference between
a $2K page and a $15K page is rarely the design.
Both might look clean and modern. Both might have good copy.

The difference is what happens after a visitor arrives.

A $2K page has a contact form that sends an email. The
email gets buried. The lead goes cold. The agency never
knows whether that visitor came from Google, a referral, or a paid ad.

A $15K page has a lead capture system. Every enquiry is tracked. Every lead has a status. Every follow-up has a reminder. The agency knows exactly which marketing channels are bringing clients that actually pay.

This tutorial builds the second type. You will build a professional agency landing page in Next.js with a lead capture form powered by Formgrid. Every enquiry becomes a tracked lead with Google Sheets sync built in. No Zapier needed. No separate CRM required.

By the end, you will have a page you can use for your own agency or adapt for clients, and a lead tracking system that works from day one.

What You Are Building

Here is exactly what this tutorial produces:

A professional multi-section agency landing page in
Next.js with a hero section, services overview, social
proof, pricing packages, and a contact and quote request form at the bottom.

Every form submission goes to Formgrid, which turns it into a tracked lead instantly. The lead appears in your Formgrid dashboard with a status of New. It also appears as a new row in a Google Sheet that your whole team can see in real time. You can mark it Contacted when
you reach out, add notes from the conversation, set a follow-up reminder, and mark it Converted when it becomes a paying client.

The stack is Next.js for the frontend, Formgrid for form handling and lead tracking, and Google Sheets as
a shared team dashboard. No backend to build. No
database to manage. No Zapier bill at the end of the month.

Why Most Agency Sites Fail to Convert

Before building anything, it helps to understand why most agency landing pages underperform.

The most common problem is that the contact form is
treated as an afterthought. It sits on a separate Contact page that most visitors never navigate to deliberately.
They land on the homepage, browse the portfolio, and leave without making contact because the path to enquiring is one step too long.

The second problem is that there is no system for what happens after the enquiry arrives. It lands in an email inbox.
The agency responds when they can. There is no record of which enquiries got followed up on, which ones went cold, and no way to know which marketing channel sent them in the first place.

The third problem is that most agency sites look credible but do not build trust fast enough. Visitors need to see specific results, specific client names, and a specific next step within the first few seconds of landing on the page. Vague statements about being
passionate about design, do not convert.

This tutorial fixes all three problems.

Prerequisites

Before starting, make sure you have:

Node.js 18 or higher installed
A Formgrid account at formgrid.dev (free)
A Google account for Sheets integration
Basic familiarity with Next.js and React

Step 1: Set Up Your Next.js Project

Create a new Next.js project:

npx create-next-app@latest agency-landing \
  --typescript --tailwind --app
cd agency-landing
npm run dev
Enter fullscreen mode Exit fullscreen mode

Open localhost:3000 and confirm the default Next.js page loads.

Step 2: Create Your Formgrid Form

Before writing any code, set up your Formgrid form so you have the endpoint URL ready.

Go to formgrid.dev and sign
up using Google or email. No credit card required.


Click New Form and name it something like "Agency
Enquiries" or "Proposal Request Form".
Click Create Form.

Go to the Overview tab and copy your endpoint URL.
It will look like this:
https://api.formgrid.dev/api/f/form_id

Save this URL. You will use it in your form component.

Step 3: Build the Page Structure

Replace the contents of app/page.tsx with this
base structure:

import Hero from '@/components/Hero'
import Services from '@/components/Services'
import SocialProof from '@/components/SocialProof'
import Pricing from '@/components/Pricing'
import ContactForm from '@/components/ContactForm'

export default function Home() {
  return (
    <main>
      <Navbar/>
      <Hero />
      <Services />
      <SocialProof />
      <Pricing />
      <ContactForm />
    </main>
  )
}
Enter fullscreen mode Exit fullscreen mode

Create a components folder inside app and build
each section as a separate component.

Step 4: Build the Hero Section

Create app/components/Hero.tsx:

import Image from 'next/image'

const metrics = [
    { label: 'Revenue generated for clients', value: '$6.8M' },
    { label: 'Average increase in qualified leads', value: '214%' },
    { label: 'Projects shipped with measurable growth', value: '92' },
]

export default function Hero() {
    return (
        <section className="relative overflow-hidden bg-[#06070b] px-6 pt-28 pb-20 text-white md:pt-36">
            <div className="absolute inset-0 bg-[radial-gradient(circle_at_20%_20%,rgba(250,204,21,0.2),transparent_35%),radial-gradient(circle_at_80%_20%,rgba(56,189,248,0.15),transparent_30%)]" />
            <div className="relative mx-auto grid w-full max-w-6xl items-center gap-14 lg:grid-cols-[1fr_1fr]">
                <div>
                    <p className="mb-5 text-xs font-semibold uppercase tracking-[0.32em] text-amber-300">
                        Precision web design for serious growth
                    </p>
                    <h1 className="font-display max-w-3xl text-4xl font-black leading-[0.92] tracking-[-0.02em] text-white md:text-5xl lg:text-6xl">
                        Your next website should feel premium and perform like a sales team
                    </h1>
                    <p className="mt-7 max-w-xl text-base leading-7 text-slate-300 md:text-lg">
                        We design fast conversion ready websites for agencies and software
                        brands that need more demand from the same traffic.
                    </p>

                    <div className="mt-10 flex flex-wrap gap-4">
                        <a
                            href="#contact"
                            className="rounded-full bg-amber-300 px-7 py-3 text-sm font-semibold uppercase tracking-[0.2em] text-slate-900 transition hover:bg-amber-200"
                        >
                            Book your strategy call
                        </a>
                        <a
                            href="#services"
                            className="rounded-full border border-slate-600 px-7 py-3 text-sm font-semibold uppercase tracking-[0.2em] text-slate-200 transition hover:border-slate-400 hover:text-white"
                        >
                            Explore capabilities
                        </a>
                    </div>

                    <div className="mt-14 grid gap-4 sm:grid-cols-3">
                        {metrics.map((metric) => (
                            <div
                                key={metric.label}
                                className="rounded-2xl border border-white/10 bg-white/[0.03] p-4 backdrop-blur"
                            >
                                <p className="text-3xl font-semibold text-amber-300">{metric.value}</p>
                                <p className="mt-2 text-xs uppercase tracking-[0.16em] text-slate-400">
                                    {metric.label}
                                </p>
                            </div>
                        ))}
                    </div>
                </div>

                <div className="relative">
                    <div className="absolute -inset-4 rounded-[2rem] bg-gradient-to-tr from-amber-300/30 to-cyan-300/20 blur-2xl" />
                    <div className="relative overflow-hidden rounded-[2rem] border border-white/10 bg-slate-950/70 p-3 shadow-[0_24px_80px_rgba(0,0,0,0.55)]">
                        <Image
                            src="https://images.unsplash.com/photo-1497366811353-6870744d04b2?auto=format&fit=crop&w=1600&q=80"
                            alt="Creative studio workspace with premium website design presentation"
                            width={1200}
                            height={900}
                            className="h-[390px] w-full rounded-[1.5rem] object-cover md:h-[470px] lg:h-[530px]"
                            priority
                        />
                        <div className="mt-4 rounded-xl border border-white/10 bg-black/40 p-4">
                            <p className="text-xs uppercase tracking-[0.2em] text-slate-300">
                                Active project dashboard
                            </p>
                            <p className="mt-2 text-sm text-slate-200">
                                Lead conversion this month is up 39 percent after launch
                            </p>
                        </div>
                    </div>
                </div>
            </div>
        </section>
    )
}
Enter fullscreen mode Exit fullscreen mode

Step 5: Build the Navigation Bar

Create app/components/Navbar.tsx

'use client'

import { useState } from 'react'

const navItems = [
  { label: 'Services', href: '#services' },
  { label: 'Results', href: '#results' },
  { label: 'Pricing', href: '#pricing' },
  { label: 'Contact', href: '#contact' },
]

export default function Navbar() {
  const [isMenuOpen, setIsMenuOpen] = useState(false)

  const handleNavClick = (
    e: React.MouseEvent<HTMLAnchorElement>,
    href: string
  ) => {
    e.preventDefault()
    const target = document.querySelector(href)
    if (!target) return
    target.scrollIntoView({ behavior: 'smooth', block: 'start' })
    setIsMenuOpen(false)
  }

  return (
    <header className="fixed inset-x-0 top-0 z-50 px-6 pt-5">
      <nav className="mx-auto w-full max-w-6xl rounded-2xl border border-white/10 bg-slate-950/70 px-5 py-3 backdrop-blur md:px-7">
        <div className="flex items-center justify-between">
          <a href="#" className="text-sm font-semibold uppercase tracking-[0.2em] text-white">
            AGENCY STUDIO
          </a>

          <ul className="hidden items-center gap-7 md:flex">
            {navItems.map((item) => (
              <li key={item.href}>
                <a
                  href={item.href}
                  onClick={(e) => handleNavClick(e, item.href)}
                  className="text-xs font-semibold uppercase tracking-[0.18em] text-slate-300 transition hover:text-white"
                >
                  {item.label}
                </a>
              </li>
            ))}
          </ul>

          <div className="flex items-center gap-3">
            <a
              href="#contact"
              onClick={(e) => handleNavClick(e, '#contact')}
              className="hidden rounded-full bg-amber-300 px-4 py-2 text-xs font-semibold uppercase tracking-[0.16em] text-slate-900 transition hover:bg-amber-200 md:inline-block md:px-5"
            >
              Book call
            </a>
            <button
              type="button"
              aria-label="Toggle navigation menu"
              aria-expanded={isMenuOpen}
              onClick={() => setIsMenuOpen((prev) => !prev)}
              className="inline-flex h-10 w-10 items-center justify-center rounded-full border border-white/15 text-slate-200 transition hover:border-white/30 hover:text-white md:hidden"
            >
              <span className="text-lg">{isMenuOpen ? 'x' : '='}</span>
            </button>
          </div>
        </div>

        {isMenuOpen && (
          <div className="mt-4 border-t border-white/10 pt-4 md:hidden">
            <ul className="space-y-3">
              {navItems.map((item) => (
                <li key={`mobile-${item.href}`}>
                  <a
                    href={item.href}
                    onClick={(e) => handleNavClick(e, item.href)}
                    className="block rounded-lg px-2 py-2 text-xs font-semibold uppercase tracking-[0.18em] text-slate-200 transition hover:bg-white/5 hover:text-white"
                  >
                    {item.label}
                  </a>
                </li>
              ))}
            </ul>
            <a
              href="#contact"
              onClick={(e) => handleNavClick(e, '#contact')}
              className="mt-4 inline-block rounded-full bg-amber-300 px-5 py-2 text-xs font-semibold uppercase tracking-[0.16em] text-slate-900 transition hover:bg-amber-200"
            >
              Book call
            </a>
          </div>
        )}
      </nav>
    </header>
  )
}

Enter fullscreen mode Exit fullscreen mode




Step 6: Build the Services Section

Create app/components/Services.tsx:

import Image from 'next/image'

const services = [
  {
    title: 'Demand focused websites',
    description:
      'A complete website system built to guide visitors from curiosity to qualified enquiry.',
    deliverables: [
      'Messaging architecture',
      'Visual identity direction',
      'Conversion focused pages',
    ],
    image:
      'https://images.unsplash.com/photo-1487014679447-9f8336841d58?auto=format&fit=crop&w=1600&q=80',
  },
  {
    title: 'Premium launch experiences',
    description:
      'Launch pages and campaign experiences engineered to make your offer feel impossible to ignore.',
    deliverables: [
      'Launch strategy workshop',
      'Sales page copy structure',
      'Rapid split test setup',
    ],
    image:
      'https://images.unsplash.com/photo-1460925895917-afdab827c52f?auto=format&fit=crop&w=1600&q=80',
  },
  {
    title: 'Client acquisition systems',
    description:
      'Connected forms analytics and follow up logic so every qualified lead reaches your team instantly.',
    deliverables: [
      'Advanced enquiry flows',
      'CRM and sheet automation',
      'Performance reporting',
    ],
    image:
      'https://images.unsplash.com/photo-1552664730-d307ca884978?auto=format&fit=crop&w=1600&q=80',
  },
]

export default function Services() {
  return (
    <section id="services" className="bg-[#090b10] px-6 py-24 text-white md:py-32">
      <div className="mx-auto w-full max-w-6xl">
        <div className="mb-14 flex flex-wrap items-end justify-between gap-8">
          <div>
            <p className="text-xs font-semibold uppercase tracking-[0.32em] text-cyan-300">
              What your investment includes
            </p>
            <h2 className="mt-4 max-w-3xl text-4xl font-semibold leading-tight text-white md:text-5xl">
              Every engagement combines strategic thinking with polished execution
            </h2>
          </div>
          <p className="max-w-md text-sm leading-7 text-slate-300">
            You work directly with senior designers and developers from kickoff to
            launch so your project keeps speed and quality.
          </p>
        </div>

        <div className="grid gap-6 lg:grid-cols-3">
          {services.map((service) => (
            <div
              key={service.title}
              className="overflow-hidden rounded-3xl border border-white/10 bg-white/[0.03] shadow-[0_20px_50px_rgba(0,0,0,0.35)] transition hover:-translate-y-1 hover:border-cyan-300/30"
            >
              <Image
                src={service.image}
                alt={service.title}
                width={1000}
                height={700}
                className="h-52 w-full object-cover"
              />
              <div className="p-7">
                <h3 className="text-2xl font-semibold text-white">{service.title}</h3>
                <p className="mt-3 text-sm leading-7 text-slate-300">
                  {service.description}
                </p>
                <ul className="mt-6 space-y-2">
                  {service.deliverables.map((item) => (
                    <li key={item} className="text-sm text-slate-200">
                      {item}
                    </li>
                  ))}
                </ul>
                <a
                  href="#contact"
                  className="mt-7 inline-block rounded-full border border-slate-500 px-5 py-2 text-xs font-semibold uppercase tracking-[0.2em] text-slate-200 transition hover:border-cyan-300 hover:text-cyan-200"
                >
                  Start this scope
                </a>
              </div>
            </div>
          ))}
        </div>

        <div className="mt-10 rounded-3xl border border-white/10 bg-slate-950/70 p-8 md:p-10">
          <p className="text-xs font-semibold uppercase tracking-[0.2em] text-cyan-300">
            Delivery confidence
          </p>
          <p className="mt-3 max-w-4xl text-lg leading-8 text-slate-200">
            Every project follows a clear sequence with weekly checkpoints design
            review sessions and launch QA so your team sees progress at every step.
          </p>
        </div>
      </div>
    </section>
  )
}
Enter fullscreen mode Exit fullscreen mode

Step 6: Build the Social Proof Section

Create app/components/SocialProof.tsx:

import Image from 'next/image'

const testimonials = [
  {
    quote:
      'This launch gave our team instant credibility. Clients now say our site looks like the top firm in our market.',
    name: 'Elena Ross',
    title: 'Managing Partner at Meridian Legal',
    image:
      'https://images.unsplash.com/photo-1487412720507-e7ab37603c6f?auto=format&fit=crop&w=500&q=80',
  },
  {
    quote:
      'Our enquiry quality improved within the first week. We closed more deals without increasing ad spend.',
    name: 'James Holloway',
    title: 'Founder at North Ridge Growth',
    image:
      'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&w=500&q=80',
  },
  {
    quote:
      'The experience feels premium from first click to submission. Every detail reflects confidence and trust.',
    name: 'Priya Raman',
    title: 'Marketing Director at Atlas Health',
    image:
      'https://images.unsplash.com/photo-1544005313-94ddf0286df2?auto=format&fit=crop&w=500&q=80',
  },
]

export default function SocialProof() {
  return (
    <section id="results" className="bg-[#06070b] px-6 py-24 text-white md:py-32">
      <div className="mx-auto w-full max-w-6xl">
        <div className="mb-14 flex flex-wrap items-end justify-between gap-8">
          <div>
            <p className="text-xs font-semibold uppercase tracking-[0.32em] text-amber-300">
              Trusted by ambitious teams
            </p>
            <h2 className="mt-4 text-4xl font-semibold leading-tight text-white md:text-5xl">
              Client stories from recent launches
            </h2>
          </div>
          <div className="rounded-2xl border border-white/10 bg-white/[0.03] px-6 py-4">
            <p className="text-xs uppercase tracking-[0.2em] text-slate-400">
              Average launch rating
            </p>
            <p className="mt-2 text-3xl font-semibold text-amber-300">4.98</p>
          </div>
        </div>

        <div className="grid gap-6 md:grid-cols-3">
          {testimonials.map((t) => (
            <article
              key={t.name}
              className="rounded-3xl border border-white/10 bg-white/[0.03] p-7"
            >
              <div className="mb-5 flex items-center gap-4">
                <Image
                  src={t.image}
                  alt={t.name}
                  width={70}
                  height={70}
                  className="h-14 w-14 rounded-full border border-white/15"
                />
                <div>
                  <p className="text-sm font-semibold text-white">{t.name}</p>
                  <p className="text-xs uppercase tracking-[0.14em] text-slate-400">
                    {t.title}
                  </p>
                </div>
              </div>
              <p className="text-sm leading-7 text-slate-200">
                &quot;{t.quote}&quot;
              </p>
            </article>
          ))}
        </div>
      </div>
    </section>
  )
}

Enter fullscreen mode Exit fullscreen mode


Step 7: Build the Pricing Section

Create app/components/Pricing.tsx:

const packages = [
  {
    name: 'Essential',
    price: '$6000',
    description: 'For teams that need a refined digital presence quickly',
    features: [
      'Complete homepage and service pages',
      'Offer and messaging workshop',
      'Lead enquiry form and automation setup',
      'Performance setup and launch QA',
    ],
  },
  {
    name: 'Signature',
    price: '$11000',
    description: 'Our most selected package for serious growth campaigns',
    features: [
      'High fidelity brand aligned design system',
      'Custom copy structure for each major page',
      'Conversion journey and analytics map',
      'Team enablement and launch training session',
      'Thirty day optimization support',
    ],
    featured: true,
  },
  {
    name: 'Flagship',
    price: '$15000',
    description: 'Complete world class website program for market leaders',
    features: [
      'Full strategy discovery and growth planning',
      'Premium UI system and advanced interactions',
      'Integrated lead qualification logic',
      'Content framework and authority sections',
      'Ninety day advisory support after launch',
    ],
  },
]

export default function Pricing() {
  return (
    <section id="pricing" className="bg-[#090b10] px-6 py-24 text-white md:py-32">
      <div className="mx-auto w-full max-w-6xl">
        <p className="text-xs font-semibold uppercase tracking-[0.32em] text-amber-300">
          Investment tiers
        </p>
        <h2 className="mt-4 text-4xl font-semibold leading-tight text-white md:text-5xl">
          Pick the level that matches your growth targets
        </h2>

        <div className="mt-12 grid gap-6 lg:grid-cols-3">
          {packages.map((pkg) => (
            <article
              key={pkg.name}
              className={`rounded-3xl border p-8 ${
                pkg.featured
                  ? 'border-amber-300 bg-amber-300 text-slate-900 shadow-[0_20px_60px_rgba(250,204,21,0.22)]'
                  : 'border-white/10 bg-white/[0.03]'
              }`}
            >
              <p
                className={`text-xs font-semibold uppercase tracking-[0.2em] ${
                  pkg.featured ? 'text-slate-800' : 'text-amber-300'
                }`}
              >
                {pkg.name}
              </p>
              <p className="mt-4 text-5xl font-semibold">{pkg.price}</p>
              <p
                className={`mt-3 text-sm leading-7 ${
                  pkg.featured ? 'text-slate-800' : 'text-slate-300'
                }`}
              >
                {pkg.description}
              </p>

              <ul className="mt-7 space-y-3">
                {pkg.features.map((feature) => (
                  <li key={feature} className="flex items-start gap-3 text-sm">
                    <span className={pkg.featured ? 'text-slate-900' : 'text-amber-300'}>
                      ✓
                    </span>
                    <span>{feature}</span>
                  </li>
                ))}
              </ul>
            </article>
          ))}
        </div>
      </div>
    </section>
  )
}
Enter fullscreen mode Exit fullscreen mode

Step 8: Build the Contact Form With Formgrid

This is the most important section. Create
app/components/ContactForm.tsx:

'use client'

import Image from 'next/image'
import { useState } from 'react'

export default function ContactForm() {
    const [status, setStatus] = useState<'idle' | 'sending' | 'success' | 'error'>(
        'idle'
    )

    const FORMGRID_ENDPOINT =
        'https://api.formgrid.dev/forms/your-form-id/submissions'

    async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
        e.preventDefault()

        const form = e.currentTarget
        const honey = (form.elements.namedItem('_honey') as HTMLInputElement).value

        if (honey) return

        setStatus('sending')

        const formData = new FormData(form)

        const name = `${formData.get('firstName') ?? ''} ${formData.get('lastName') ?? ''}`.trim()
        const service = (formData.get('service') as string) ?? 'Unknown service'
        const budget = (formData.get('budget') as string) ?? 'No budget selected'

        formData.set('_subject', `New Enquiry: ${name} | ${service} | ${budget}`)

        try {
            const res = await fetch(FORMGRID_ENDPOINT, {
                method: 'POST',
                body: formData,
            })

            if (res.ok) {
                setStatus('success')
            } else {
                setStatus('error')
            }
        } catch {
            setStatus('error')
        }
    }

    return (
        <section id="contact" className="bg-[#06070b] px-6 py-24 text-white md:py-32">
            <div className="mx-auto grid w-full max-w-6xl gap-8 lg:grid-cols-[0.9fr_1.1fr]">
                <aside className="rounded-3xl border border-white/10 bg-white/[0.03] p-6 md:p-8">
                    <p className="text-xs font-semibold uppercase tracking-[0.3em] text-amber-300">
                        Let us build your next growth engine
                    </p>
                    <h2 className="mt-4 text-3xl font-semibold leading-tight md:text-4xl">
                        Request your custom proposal
                    </h2>
                    <p className="mt-4 text-sm leading-7 text-slate-300">
                        Share your goals and our team will send scope pricing and timeline
                        recommendations within one business day.
                    </p>
                    <Image
                        src="https://images.unsplash.com/photo-1552664730-d307ca884978?auto=format&fit=crop&w=1600&q=80"
                        alt="Agency team reviewing website strategy"
                        width={1000}
                        height={1000}
                        className="mt-6 rounded-2xl border border-white/10"
                    />
                </aside>

                {status === 'success' ? (
                    <div className="rounded-3xl border border-amber-300 bg-amber-300/10 p-10 text-center">
                        <p className="text-xs font-semibold uppercase tracking-[0.25em] text-amber-200">
                            Enquiry received
                        </p>
                        <p className="mt-4 text-3xl font-semibold text-white">Thank you</p>
                        <p className="mt-3 text-sm text-slate-200">
                            We will reply with your proposal details within one business day.
                        </p>
                    </div>
                ) : (
                    <form
                        onSubmit={handleSubmit}
                        className="rounded-3xl border border-white/10 bg-slate-950/60 p-6 md:p-8"
                    >
                        <input
                            type="text"
                            name="_honey"
                            style={{ display: 'none' }}
                            tabIndex={-1}
                            autoComplete="off"
                        />

                        <div className="grid gap-4 sm:grid-cols-2">
                            <label className="text-sm text-slate-300">
                                First name
                                <input
                                    type="text"
                                    id="firstName"
                                    name="firstName"
                                    required
                                    placeholder="Jordan"
                                    className="mt-2 w-full rounded-xl border border-slate-700 bg-slate-900 px-4 py-3 text-white outline-none transition focus:border-amber-300"
                                />
                            </label>

                            <label className="text-sm text-slate-300">
                                Last name
                                <input
                                    type="text"
                                    id="lastName"
                                    name="lastName"
                                    required
                                    placeholder="Reed"
                                    className="mt-2 w-full rounded-xl border border-slate-700 bg-slate-900 px-4 py-3 text-white outline-none transition focus:border-amber-300"
                                />
                            </label>
                        </div>

                        <label className="mt-4 block text-sm text-slate-300">
                            Email address
                            <input
                                type="email"
                                id="email"
                                name="email"
                                required
                                placeholder="jordan@company.com"
                                className="mt-2 w-full rounded-xl border border-slate-700 bg-slate-900 px-4 py-3 text-white outline-none transition focus:border-amber-300"
                            />
                        </label>

                        <div className="mt-4 grid gap-4 sm:grid-cols-2">
                            <label className="text-sm text-slate-300">
                                Service needed
                                <select
                                    id="service"
                                    name="service"
                                    required
                                    className="mt-2 w-full rounded-xl border border-slate-700 bg-slate-900 px-4 py-3 text-white outline-none transition focus:border-amber-300"
                                >
                                    <option value="">Select service</option>
                                    <option>Demand focused website</option>
                                    <option>Premium launch experience</option>
                                    <option>Client acquisition system</option>
                                </select>
                            </label>

                            <label className="text-sm text-slate-300">
                                Budget range
                                <select
                                    id="budget"
                                    name="budget"
                                    className="mt-2 w-full rounded-xl border border-slate-700 bg-slate-900 px-4 py-3 text-white outline-none transition focus:border-amber-300"
                                >
                                    <option value="">Select range</option>
                                    <option>$6000 to $9000</option>
                                    <option>$10000 to $14000</option>
                                    <option>$15000 and above</option>
                                </select>
                            </label>
                        </div>

                        <label className="mt-4 block text-sm text-slate-300">
                            Project goals
                            <textarea
                                id="message"
                                name="message"
                                rows={6}
                                required
                                placeholder="Tell us what you are launching who you want to reach and what success looks like."
                                className="mt-2 w-full rounded-xl border border-slate-700 bg-slate-900 px-4 py-3 text-white outline-none transition focus:border-amber-300"
                            />
                        </label>

                        <button
                            type="submit"
                            disabled={status === 'sending'}
                            className="mt-6 rounded-full bg-amber-300 px-7 py-3 text-sm font-semibold uppercase tracking-[0.2em] text-slate-900 transition hover:bg-amber-200 disabled:cursor-not-allowed disabled:opacity-60"
                        >
                            {status === 'sending' ? 'Sending' : 'Send proposal request'}
                        </button>

                        {status === 'error' && (
                            <p className="mt-4 text-sm text-red-300">
                                Something went wrong. Please try again or contact us by email.
                            </p>
                        )}
                    </form>
                )}
            </div>
        </section>
    )
}
Enter fullscreen mode Exit fullscreen mode

Replace your-form-id in the FORMGRID_ENDPOINT
constant with your actual Formgrid form ID from
Step 2.

Step 9: Connect Google Sheets

In your Formgrid dashboard, go to your form settings
and click the Integrations tab. Connect your Google
account and select the sheet where you want enquiries
to land.




From this point, every form submission creates a new row in that sheet automatically. The columns match your form fields exactly. Your team can see new enquiries in real time without logging into anything.

Step 10: Test the Full Flow

Start your development server:

npm run dev
Enter fullscreen mode Exit fullscreen mode

Navigate to the contact form section and submit a test enquiry with real-looking data.



Within seconds, you should see three things happen.

The submission also appears in your
Leads tab as a tracked lead with
a status, a timestamp, and all the Submitter details.

The submission appears in your Formgrid dashboard with a status of New.

An email notification arrives at your Formgrid signup email with the full submission details.

A new row appears in your connected Google Sheet with every field populated automatically.

If all three appear, your lead capture system is working correctly.

The Lead Pipeline in Action

Go to your Formgrid dashboard and click on the test submission you just created.

You will see the lead with a status of New. This is the starting state for every enquiry that arrives.

Click the status and change it to Contacted.
This records that you have reached out to this person.
Your conversion rate updates automatically.

Click Add Note and type something like "Spoke on
the phone. Interested in the Growth package.
Needs two weeks to confirm budget." That note lives on the lead permanently.

Click Set Follow Up and choose a date five days from now.
On that date, Formgrid will email you with the lead details and your note attached. You will not need to remember to follow up. The
reminder does it for you.



This is the difference between an agency that closes 40% of its enquiries and one that closes 15%. Not skill. Not price. Just follow up on consistency.

What Makes This a $15K Page

The page you just built has every element that separates a premium agency site from a basic one.

A clear value proposition in the hero that speaks directly to the outcome the client wants. Not "we are passionate about design" but "we build websites that convert."

Specific social proof with real numbers. Percentage lift in conversions. Revenue generated. Projects delivered. Vague testimonials do not build trust. Specific results do.

Service packages with transparent pricing. Clients
who see pricing upfront are more qualified when they
enquire. They have already decided the price is acceptable before filling out the form.

A strong single call to action. Every section points to the same place. Get a Quote. Nothing competes with it.

Lead tracking is built in from day one. This is the element most agency sites completely miss. Every enquiry is captured, tracked, and followed up on systematically. No lead goes cold because someone forgot to reply.

The last point is what justifies the $15K price tag to clients. You are not just delivering a website. You are delivering a system that actively generates and tracks revenue for their business.

Using This for Your Own Agency

If you are building this for your own freelance or agency business, replace the placeholder copy with your real services, real results, and real pricing.

Add your actual testimonials. Replace the statistics
with real numbers from your own projects. Update the service descriptions to match what you actually build.

The form endpoint is already connected to Formgrid.
The Google Sheets sync is already configured. The lead pipeline is already working.

Every client enquiry that comes in through this page will appear in your dashboard as a tracked lead, in your Google Sheet as a new row, and in your inbox as an email notification within seconds of submission.

Deploying to Production

When you are ready to go live, deploy to Vercel in one command:

npx vercel
Enter fullscreen mode Exit fullscreen mode

Vercel detects Next.js automatically and deploys with zero configuration. Your form will work identically in production because Formgrid handles all the server-side processing.

Final Thoughts

The gap between a $2K website and a $15K website
is not the design framework, the number of animations, or the size of the portfolio section.

It is whether the site has a system for turning visitors into trackable, followable, convertible leads.

A beautiful static site with a contact form that sends emails is a $2K site. The same design with Formgrid powering the form, Google Sheets sync running in the background, and a lead pipeline
tracking every enquiry from New to Converted is a $15K site.

The only technical difference is one endpoint URL and a ten-minute Formgrid setup.

If you want to build this for your own agency or for a client, start free at
formgrid.dev. The form
backend, lead pipeline, and Google Sheets sync
are all included from day one.

For a done-for-you version, where we build the entire system for you, see our
agency setup packages.

👉 Start free at formgrid.dev

Top comments (0)