All code

Repository

FreelanceStack

A review site that scores thirty pieces of freelancer software and compares them head to head, built to be found by people searching for exactly that comparison.

Thirty tools, five weighted dimensions, one hundred and seventy-eight pages.


What it is

An independent review site for freelancer software: thirty tools scored across five weighted dimensions, rendered as a homepage, thirty reviews, thirty discount pages, eleven categories, and sixty-two head-to-head comparisons. The comparison page is the unit of value — somebody searching for one tool against another has already decided to buy and is choosing.

Why it exists

A distribution experiment, not a software problem. Affiliate review sites work when they rank, and ranking for long-tail comparison queries is a content-generation problem at a scale nobody wants to write by hand: thirty tools produce hundreds of plausible pairings. The machine was built once, proved in one market, and pointed at a second — the rubric, the comparison generator and the prerender pipeline are market-independent, and only the dataset changes.

As a business

The only thing here where revenue was the explicit goal. Affiliate commission earned by ranking for purchase-intent queries, with near-zero cost because the output is static — content cost once, traffic compounds or it does not. The honest caveat is that it is a thin-content risk by design: search engines penalise programmatic affiliate comparisons, and the line between a useful comparison site and a doorway page is drawn by somebody else's algorithm.

Stack

Client
React · Vite · Tailwind
Data
Supabase
Build
Custom prerender pipeline

The decision

The problem

Programmatic SEO fails in a specific way: sixty-two comparison pages assembled from a template are sixty-two near-duplicate pages, which rank for nothing and can penalise the whole domain. The pages have to differ substantively, not just in the two names in the headline.

What I did

Score first, generate second. Because every tool carries a five-dimension weighted score, a comparison is derived rather than templated — the dimensions where two tools actually diverge are computed, and those differences are what the page is about. Two tools that score alike on value but differ on depth produce a different page from two that differ on price.

What it costs

Entirely dependent on the scores being real. If the ratings are casually assigned, every derived page inherits that arbitrariness and the whole site becomes the thin content it was trying not to be. The rubric is the load-bearing part, and it is human-authored.

Where it ended

Shipped and live in a single commit, and not touched since. Whether that is finished or abandoned depends on traffic data.

A piece of the code

src/data/tools.ts

const SCORE_WEIGHTS = {  value_for_money: 0.35,  ease_of_use: 0.25,  feature_depth: 0.18,  integrations: 0.12,  customer_support: 0.1,} as const;/** * Convert the five 0–100 dimension scores into a single 0–5 overall, weighted * to match what freelancers actually care about (value, then ease). */export function computeOverall(t: Pick<Tool, keyof typeof SCORE_WEIGHTS>): number {  const weighted =    t.value_for_money * SCORE_WEIGHTS.value_for_money +    t.ease_of_use * SCORE_WEIGHTS.ease_of_use +    t.feature_depth * SCORE_WEIGHTS.feature_depth +    t.integrations * SCORE_WEIGHTS.integrations +    t.customer_support * SCORE_WEIGHTS.customer_support;  return Math.round((weighted / 20) * 10) / 10; // 0–100 → 0–5, one decimal}
One hundred and seventy-eight pages are derived from these five numbers. That is the whole bet: a comparison page is only substantively different from a template if the two tools it compares actually diverge on a dimension, and divergence is only meaningful if the dimensions are weighted deliberately. Re-tune the weights here and every page on the site changes. Get them wrong and the site is the thin content it was built to avoid.

Excerpt — trimmed for reading, not a full file.

Ask about this project