After the Weekend Sprint: Three Features That Made swisscontract.ai Actually Useful
Source: Dev.to
SwissContract.ai – From Weekend Hack to Production‑Ready Tool
I shipped SwissContract.ai in a weekend. Told the story. Thought I was done. Then Monday happened.
The three cracks that showed up
| Issue | Why it mattered |
|---|---|
| Scanned PDFs returned empty analysis | The error “Contract text is too short or empty” is useless when a user is trying to analyse a lease that was scanned. |
| English‑only UI | Switzerland has four official languages. An English‑only contract tool is a bad joke for local users. |
| Site invisible to search engines | No og:image, no sitemap, no structured data – the site never appears in search results. |
These weren’t nice‑to‑haves; they were blocking real use.
1️⃣ Handling scanned PDFs
Most contracts uploaded are digital PDFs (copy‑pasteable text). A significant chunk, however, are image‑only PDFs.
My original text‑extraction library (unpdf) returns an empty string for those files, causing the “too short” error.
First attempt – OCR in Node.js
- Render PDF pages to a canvas with
pdfjs-dist. - Use OCR on the canvas output.
Problem: OffscreenCanvas isn’t available in Node 22 or on Vercel, so the approach that works in a browser dies on a serverless backend.
The solution – Claude’s native PDF support
Claude can accept a raw PDF (base64‑encoded) and perform text extraction internally. No extra canvas, no OCR library, no server‑side dependencies.
// When text extraction fails ( pathname === `/${l}` || pathname.startsWith(`/${l}/`)
);
if (pathLocale) {
const response = NextResponse.next();
response.cookies.set("locale", pathLocale, { path: "/", maxAge: 31536000 });
return response;
}
// 2️⃣ Root path only → try cookie, then Accept‑Language header
if (pathname === "/") {
const cookieLocale = request.cookies.get("locale")?.value;
if (cookieLocale && cookieLocale !== "en") {
return NextResponse.redirect(new URL(`/${cookieLocale}`, request.url));
}
const detected = detectLocale(request.headers.get("accept-language"));
if (detected && detected !== "en") {
const response = NextResponse.redirect(
new URL(`/${detected}`, request.url)
);
response.cookies.set("locale", detected, { path: "/", maxAge: 31536000 });
return response;
}
}
// 3️⃣ No special handling → continue as‑is
return NextResponse.next();
}
Result
- Each language now has a stable, crawlable URL.
- Google discovers
/de,/fr,/itas separate pages → properhreflanghandling. - Shared links always point to the intended language.
- Analytics can segment by pathname.
🎉 Takeaways
- Leverage existing AI capabilities – Claude’s PDF handling saved us from building a custom OCR pipeline.
- Design i18n with SEO in mind – Locale‑specific URLs are essential for discoverability and analytics.
- Middleware can keep the user experience smooth while still serving the correct language version.
SwissContract.ai went from a weekend prototype to a production‑ready, multilingual, SEO‑friendly contract analyser. 🚀
Overview
Problem:
- Cookie‑based locale handling broke SEO, link sharing, and analytics.
- The site shipped without essential SEO fundamentals.
Lesson:
- Path‑based routing is the right default for public‑facing sites.
- Ship SEO basics before announcing a product.
What Went Wrong
| Issue | Impact |
|---|---|
No og:image | Social shares displayed a blank card |
| No sitemap | Search engines couldn’t discover pages efficiently |
No robots.txt | Crawlers had no guidance |
| No structured data (FAQPage, Organization) | Missed rich‑result opportunities |
| Thin homepage content (no “How it works”, no FAQ) | Poor user experience & lower rankings |
| Cookie‑based locale (instead of path‑based) | SEO, link sharing, and analytics broke |
Fixes Implemented (in one session)
1. Open Graph Image
- Added an SVG (
/public/og-image.svg) – 1200 × 630, fully vector, no rasterisation needed. - Served directly by Next.js.
2. Sitemap (Next.js 14)
// app/sitemap.ts
import type { MetadataRoute } from 'next';
export default function sitemap(): MetadataRoute.Sitemap {
return [
{
url: "https://swisscontract.ai",
lastModified: new Date(),
changeFrequency: "weekly",
priority: 1,
},
];
}
3. Structured Data (JSON‑LD)
Added FAQPage and Organization schemas to layout.tsx.
Google now can surface FAQ rich results in SERPs.
4. Homepage Content
- Added a FAQ section and a “How it works” step‑by‑step guide.
- These appear only when no analysis is active, keeping the UI clean.
5. Locale Routing
- Switched from cookie‑based locale to path‑based (
/de,/fr, …). - SEO‑friendly and works as the source of truth for language.
Product Summary
+-------------------------------------------------+
| swisscontract.ai |
| |
| Input: PDF / DOCX / DOC / TXT |
| (text‑based OR scanned) |
| |
| Language: EN / DE / FR / IT |
| (path‑based, SEO‑friendly) |
| |
| Output: Plain‑language AI analysis |
| in the user's language |
| |
| Limits: 5 MB, 20 pages, 5 IP/day |
| Storage: none |
+-------------------------------------------------+
- Scanned PDF support – the AI model reads PDFs natively; the simplest solution turned out to be the most powerful.
Takeaway
“Ship SEO basics before you tell anyone about the product.”
Skipping those fundamentals cost early traffic and required a painful retro‑fit. The three focused sessions that followed turned a weekend prototype into a usable, SEO‑friendly service.
Live demo: https://swisscontract.ai – analyse any Swiss contract for free, no account required.