DEV Community

Farhan Murtaza
Farhan Murtaza

Posted on

Building a 35-tool privacy-first hub with Next.js 15: lessons from 2 weeks of solo build

After 2 weeks of evenings and weekends, I shipped Toolsfluent: a free hub of 35+ online tools (calculators, converters, generators) targeting privacy-conscious users in South Asia, the Gulf, and globally.

Sharing the technical learnings here, not the marketing version. If you're a solo dev shipping a content-heavy site, this might save you a few weeks.

The stack

  • Framework: Next.js 15 (App Router, RSC, ISR)
  • Language: TypeScript
  • Styling: Tailwind CSS
  • Icons: lucide-react
  • Fonts: Inter (body) + Sora (brand) via next/font/google
  • Theme: next-themes
  • Hosting: Vercel (Hobby tier)

Static generation for every tool page. No database. No auth. Currency converter is the only thing that hits an external API (exchangerate-api.com free tier).

What worked unexpectedly well

1. Single source-of-truth for tools

Every tool's metadata lives in one TypeScript array:

export const TOOLS: Tool[] = [
  {
    slug: "mortgage-calculator",
    name: "Mortgage Calculator",
    description: "...",
    category: "finance",
    keywords: [...],
    icon: "Home",
    featured: true,
    howToSteps: [...],
    faqs: [...],
    relatedTools: [...]
  },
  // ... 34 more
];
Enter fullscreen mode Exit fullscreen mode

Adding a new tool means: add the entry, create a folder, write the calculator component. Sitemap, JSON-LD schema, search index, navigation, related tools, and category pages auto-update from the array.

This single decision saved hours per tool. By tool 20, I was shipping new tools in under an hour.

2. JSON-LD structured data on everything

Every tool ships:

  • SoftwareApplication schema
  • FAQPage schema (from FAQs in the data array)
  • BreadcrumbList

Categories ship CollectionPage. Site root ships WebSite + Organization.

Google takes JSON-LD seriously for tool pages. Without it, you're invisible. With it, your tool can show up with rating stars, FAQs in SERP, and "free" pricing badge.

Build a single buildToolJsonLd(tool) helper and you're done.

3. Per-tool meta description, canonical, OG tags via one helper

export function buildMetadata({title, description, path, keywords, image}) {
  const url = `${SITE_URL}${path}`;
  return {
    title: `${title} | ${SITE_NAME}`,
    description,
    keywords: keywords.join(", "),
    metadataBase: new URL(SITE_URL),
    alternates: { canonical: url },
    openGraph: {
      title, description, url,
      images: [{ url: image, width: 1200, height: 630 }]
    },
    twitter: { card: "summary_large_image" },
    robots: { index: true, follow: true }
  };
}
Enter fullscreen mode Exit fullscreen mode

Every page calls this. Zero metadata bugs. Easy to verify in view-source.

What surprised me

Em-dashes flagged as AI content

Google's Helpful Content classifier flags em-dashes (—) as a strong signal of AI-generated content. Removed all of them site-wide via sed and replaced with commas / colons / periods. Sounds silly but it's a real ranking heuristic.

Authoritative source citations matter on YMYL

Every health and finance blog post got 2-3 citations to authoritative sources (CDC, WHO, NIST, IRS, CFPB, RBI, GST Council India, HMRC). YMYL (Your Money or Your Life) content without citations gets de-ranked by Google's Quality Rater Guidelines.

Took an extra hour per post to research and cite properly. Made a measurable difference in indexing speed.

Canonical tag with vs without trailing slash

Spent hours debugging "Alternate page with proper canonical tag" warnings in GSC. Turned out my SITE_URL had no trailing slash but Google was crawling URLs with trailing slash. Both worked but Google was confused which one to canonicalize.

Fix: be consistent. Pick one and stick with it everywhere.

Image compressor as a Canvas API trick

Most image compressors upload to a server. The privacy-first version is just:

const img = new Image();
img.onload = () => {
  const canvas = document.createElement('canvas');
  canvas.width = img.width;
  canvas.height = img.height;
  canvas.getContext('2d').drawImage(img, 0, 0);
  canvas.toBlob(blob => downloadBlob(blob), 'image/jpeg', 0.8);
};
img.src = URL.createObjectURL(file);
Enter fullscreen mode Exit fullscreen mode

Quality knob is just the third arg to toBlob. Done. No upload, no server, no privacy concerns.

What's next

  • More Pakistani / Indian / UAE niche tools (Marla converter, UAE gratuity, Pakistani GPA already shipped)
  • WhatsApp share button site-wide (huge in PK / IN, ignored by most western tool sites)
  • AI-augmented text tools using Claude API (cover letter generator, code explainer, etc.) — text only, no image gen because paid API costs would exceed AdSense revenue at this stage

Honest numbers

35 tools live, 17 blog posts, all legal pages, GA4 + Bing + GSC verified. Indexed in Google within 24 hours of launch.

Traffic so far: a handful of visits from my own testing. Distribution is the next problem to solve, not the build.

If you want to see the production version: https://www.toolsfluent.com

Honest feedback welcome — especially on schema markup gotchas, perf optimization wins I'm missing, or tools you'd actually use.

Top comments (0)