A drone operator in Warsaw had a steady flow of enquiries, but they went cold. By the time he replied, the client had already written to someone else. His old site took around 4 seconds to load and did not rank for any of his seven services.
We rebuilt it on Next.js with two goals: get found for each service separately, and get every request onto his phone before the client opens another tab.
One page per service
He shoots weddings, real estate, infrastructure inspections, advertising, events, sports and documentation. Each of those got its own page with:
- its own schema markup;
- real photos from that type of shoot;
- a starting price;
- an FAQ written from the questions clients actually ask.
People search for "drone wedding video Warsaw" or "roof inspection drone", not for "drone services". A single generic page cannot rank for seven different intents, seven focused pages can. Starting prices also filter out people who would never book, so the requests that do come in are warmer.
The form writes straight to Telegram
No CRM, no shared inbox. The contact form posts to a Next.js route handler, which checks a rate limit in Upstash Redis and sends the request to the operator's Telegram through the Bot API. A simplified version:
// app/api/lead/route.ts
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(3, "10 m"),
});
export async function POST(req: Request) {
const ip = req.headers.get("x-forwarded-for")?.split(",")[0] ?? "unknown";
const { success } = await ratelimit.limit(`lead:${ip}`);
if (!success) return Response.json({ ok: false }, { status: 429 });
const { name, contact, service, message } = await req.json();
if (!contact || String(message ?? "").length > 2000) {
return Response.json({ ok: false }, { status: 400 });
}
const text = [
`New request: ${service}`,
`Name: ${name}`,
`Contact: ${contact}`,
"",
message,
].join("\n");
const res = await fetch(
`https://api.telegram.org/bot${process.env.TG_TOKEN}/sendMessage`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ chat_id: process.env.TG_CHAT_ID, text }),
}
);
return Response.json({ ok: res.ok }, { status: res.ok ? 200 : 502 });
}
Two small details that save trouble. Send plain text without parse_mode, otherwise one underscore in a client's email breaks Markdown parsing and the message never arrives. And return an error to the form when Telegram fails, so the visitor sees a fallback contact instead of a fake "thank you".
Indexing on every deploy
New service pages should not wait a week for a crawler. On each deploy the build sends changed URLs to IndexNow, which Bing and other participating search engines pick up:
await fetch("https://api.indexnow.org/indexnow", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
host: "example.com",
key: process.env.INDEXNOW_KEY,
urlList: changedUrls,
}),
});
Results
- LCP under 1 second on all seven service pages.
- A request reaches the operator's Telegram in under 2 seconds.
- New pages get indexed in hours rather than days.
- Visitor-to-lead conversion went from 0.8% on the old site to 4.2%.
What we took from it
For a small service business the fastest reply usually wins the job, and a form that lands in a messenger the owner already checks all day beats any CRM he will not open. Separate pages with real prices bring both search traffic and better-qualified requests.
The full case is on our site: VisionAir case study. If you need a site like this for your own business, see website development.
Top comments (0)