Dynamic pages for displaying vehicles being stripped for spares, organized by make and make/model combinations.
Routes:
/vehicles-stripping-for-spares/ - All vehicles listing page/[make]-stripping-for-spares/ - Make-specific page (e.g., /citroen-stripping-for-spares/)/[make]-[model]-stripping-for-spares/ - Make/model-specific page (e.g., /bmw-3-series-stripping-for-spares/)[make]-stripping-for-spares.astro)[make]-[model]-stripping-for-spares.astro)vehicles-stripping-for-spares.astro)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”.
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);
}
src/pages/[make]-stripping-for-spares.astro (lines 107-149)src/pages/[make]-[model]-stripping-for-spares.astro (lines 141-167, 384-388)After fix, both “Citroen” and “Citroën” vehicles should appear on /citroen-stripping-for-spares/.
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/.
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.
Updated the browseModels mapping to:
modelOnlySlug is correctly extracted from the seoModel objectconst 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>
);
})}
src/pages/[make]-stripping-for-spares.astro (lines 252-267, 1296-1313)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.
All vehicle queries must:
status IN ('available', 'active')suppliers.status = 'active'featured_image IS NOT NULL AND featured_image != ''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);
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 pages use the route pattern [make]-[model]-stripping-for-spares.astro where:
make parameter: The make slug (e.g., “mercedes-benz”)model parameter: The model-only slug (e.g., “c-class”)The full URL format is: /{makeSlug}-{modelOnlySlug}-stripping-for-spares/
Example: /mercedes-benz-c-class-stripping-for-spares/
src/pages/seo-model-pages/CLAUDE.md - Model-specific pages, breadcrumb schemasrc/lib/normalizers/makeNormalizer.tssrc/lib/normalizers/vehicleNormalizer.tssrc/lib/normalizers/modelSeoNormalizer.tssrc/lib/utils/vehicleExpiration.ts| Issue | Cause | Solution |
|---|---|---|
| Vehicles missing from make page | Diacritic mismatch | Ensure OR condition matches both variations |
| Wrong vehicle count | Featured image filter | Check featured_image IS NOT NULL AND != '' |
| Supplier vehicles not showing | Supplier inactive | Verify suppliers.status = 'active' |
| Pagination issues | Range calculation | Check ITEMS_PER_PAGE constant (30) |
| Model links going to filter instead of model page | Incorrect URL construction | Verify modelOnlySlug is correct and URL format matches route pattern |
Before modifying any files in this feature:
subagent_type: "general-purpose"