Stripping For Spares Pages

Dynamic pages for displaying vehicles being stripped for spares, organized by make and make/model combinations.

Overview

Routes:

Key Features

1. Make-Specific Pages ([make]-stripping-for-spares.astro)

2. Make/Model-Specific Pages ([make]-[model]-stripping-for-spares.astro)

3. All Vehicles Page (vehicles-stripping-for-spares.astro)

Critical Bug Fix: Diacritic Handling (January 2025)

Problem

The database contains vehicle makes with both diacritic and non-diacritic variations:

PostgreSQL’s ILIKE operator is case-insensitive but not diacritic-insensitive, so a query for %Citroen% would only match “Citroen” and miss “Citroën”.

Solution

Updated both [make]-stripping-for-spares.astro and [make]-[model]-stripping-for-spares.astro to use an OR condition that matches both variations:

// Create normalized version without diacritics
const makeNameForDB = makeName.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
const makePattern = `%${makeNameForDB}%`;
const makePatternWithDiacritics = makeName !== makeNameForDB ? `%${makeName}%` : null;

// Use OR condition to match both variations
if (makePatternWithDiacritics) {
  vehiclesQuery = vehiclesQuery.or(`make.ilike.${makePattern},make.ilike.${makePatternWithDiacritics}`);
} else {
  vehiclesQuery = vehiclesQuery.ilike('make', makePattern);
}

Files Updated

Testing

After fix, both “Citroen” and “Citroën” vehicles should appear on /citroen-stripping-for-spares/.

Problem

When clicking on model links in the “Browse Models” section, users were getting URLs like ?model=Benz instead of proper model page URLs like /mercedes-benz-c-class-stripping-for-spares/.

Root Cause

The browseModels array was using modelOnlySlug from the Map key, but wasn’t ensuring the correct slug format was used when constructing URLs. The link construction was correct, but the data structure needed to be more robust.

Solution

Updated the browseModels mapping to:

  1. Ensure modelOnlySlug is correctly extracted from the seoModel object
  2. Improve display name formatting with proper capitalization
  3. Construct URLs explicitly before rendering to ensure correct format
const browseModels = Array.from(seoModelCounts.entries())
  .filter(([_, count]) => count >= MIN_VEHICLES_FOR_MODEL_PAGE)
  .sort((a, b) => b[1] - a[1])
  .slice(0, 12)
  .map(([modelOnlySlug, count]) => {
    const sampleVehicle = allModelsData?.find(v => {
      const seoModel = getCanonicalModelObject(makeName, v.model);
      return seoModel.modelOnlySlug === modelOnlySlug;
    });
    const seoModel = sampleVehicle ? getCanonicalModelObject(makeName, sampleVehicle.model) : null;
    // Ensure we have the correct modelOnlySlug from the seoModel object
    const correctModelOnlySlug = seoModel?.modelOnlySlug || modelOnlySlug;
    return {
      modelOnlySlug: correctModelOnlySlug,
      displayName: seoModel?.model || modelOnlySlug.split('-').map((word: string) => word.charAt(0).toUpperCase() + word.slice(1)).join(' '),
      count
    };
  });

And in the template:

{browseModels.map((model, index) => {
  // Construct the full URL for the model page
  const modelPageUrl = `/${makeSlug}-${model.modelOnlySlug}-stripping-for-spares/`;
  return (
    <a href={modelPageUrl} ...>
      ...
    </a>
  );
})}

Files Updated

Testing

After fix, clicking “C-Class” on /mercedes-stripping-for-spares/ should navigate to /mercedes-benz-c-class-stripping-for-spares/ instead of adding ?model=Benz parameter.

Database Queries

Vehicle Filtering Requirements

All vehicle queries must:

  1. Filter by vehicle status: status IN ('available', 'active')
  2. Filter by supplier status: suppliers.status = 'active'
  3. Require featured images: featured_image IS NOT NULL AND featured_image != ''
  4. Handle diacritic variations in make names

Query Pattern

let vehiclesQuery = supabase
  .from('vehicles')
  .select('..., suppliers!inner(status)', { count: 'exact' })
  .or('status.eq.available,status.eq.active')
  .eq('suppliers.status', 'active')
  // Handle diacritics with OR condition
  .or(`make.ilike.%${makeNameForDB}%,make.ilike.%${makeName}%`)
  .not('featured_image', 'is', null)
  .neq('featured_image', '')
  .order('created_at', { ascending: false })
  .range(offset, offset + limit - 1);

Make Name Normalization

The normalizeMake() function in src/lib/normalizers/makeNormalizer.ts handles:

Important: When querying the database, always handle both diacritic and non-diacritic variations using the OR pattern shown above.

Model URL Structure

Model pages use the route pattern [make]-[model]-stripping-for-spares.astro where:

The full URL format is: /{makeSlug}-{modelOnlySlug}-stripping-for-spares/

Example: /mercedes-benz-c-class-stripping-for-spares/

Troubleshooting

IssueCauseSolution
Vehicles missing from make pageDiacritic mismatchEnsure OR condition matches both variations
Wrong vehicle countFeatured image filterCheck featured_image IS NOT NULL AND != ''
Supplier vehicles not showingSupplier inactiveVerify suppliers.status = 'active'
Pagination issuesRange calculationCheck ITEMS_PER_PAGE constant (30)
Model links going to filter instead of model pageIncorrect URL constructionVerify modelOnlySlug is correct and URL format matches route pattern

🤖 Subagent Rule

Before modifying any files in this feature:

  1. Spawn a subagent with subagent_type: "general-purpose"
  2. Include this CLAUDE.md path in the prompt
  3. After work is complete, update this CLAUDE.md with changes