Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
FreightUtils data updates, new tools, API changes, and MCP updates.
Subscribe via RSS: <https://www.freightutils.com/changelog.xml>

## 2026-05-15

- **Consistency**: fixed double-branded titles (root template `'%s | FreightUtils.com'` → `'%s | FreightUtils'`; `/changelog` uses `title.absolute` to avoid the "FreightUtils … FreightUtils" duplication in its own title). Dark-mode toggle now syncs across tabs via the `storage` event. `/api-docs` rate-limit copy normalised to `25 requests per day` across all three mentions (was a mix of "25 calls/day" and "25 requests per day per IP"). Email-capture (`NewsletterCapture`) added to `/hs/code/[code]` and `/hs/heading/[heading]` detail templates so HS routes match the ADR-detail/LQ-EQ pattern. Verified prior R10/R11 findings on `WebApplication`/`BreadcrumbList` JSON-LD and LQ/EQ page layout were already resolved by intervening PRs and required no action.

## 2026-05-14

- **Performance**: `/hs/code/*` and `/hs/heading/*` now served from Vercel edge cache with `Cache-Control: public, max-age=300, s-maxage=86400, stale-while-revalidate=604800`. Cold-serve response unchanged; warm-cache response served from the edge without re-rendering the page. ISR (`export const revalidate = 86400`) was already in place on both routes — this change makes the cache strategy explicit and tunable in `next.config.ts`, and surfaces the `Cache-Control` header in the response so cache hits are observable via `curl -I`. Sourced by the 2026-05-14 scraper-signature audit (`docs/audit/scraper-signature-2026-05-14.md`) which confirmed an active 216.* scraper hitting these paths at sustained ~5-second intervals. The application-layer ScrapeGuard rate limiter still runs on every request and continues to 429 the scraper as designed; Phase 1.6 will measure whether edge-cache adoption reduces overall Redis-INCR volume enough to skip Phase 2 (static generation).
Expand Down
2 changes: 1 addition & 1 deletion app/api-docs/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ export default function ApiDocsPage() {
}
}`} />
<p style={{ color: 'var(--text)', fontSize: 13, fontWeight: 600, marginTop: 16, padding: '10px 14px', background: 'rgba(232,119,34,0.08)', borderRadius: 8, border: '1px solid rgba(232,119,34,0.2)' }}>
Pro tier endpoint. Free tier: 25 calls/day. Subscribe for higher limits.
Pro tier endpoint. Free tier: 25 requests per day. Subscribe for higher limits.
</p>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion app/changelog/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const revalidate = 3600;
const ogUrl = '/api/og?title=Changelog&desc=FreightUtils+data+updates%2C+new+tools%2C+and+API+changes&badge=UPDATES';

export const metadata: Metadata = {
title: 'Changelog — FreightUtils Updates & Releases',
title: { absolute: 'Changelog — FreightUtils Updates & Releases' },
description: 'Actively maintained freight data and APIs. Data updates, new tools, API changes, and MCP releases. Subscribe via RSS at /changelog.xml.',
alternates: {
canonical: 'https://www.freightutils.com/changelog',
Expand Down
11 changes: 11 additions & 0 deletions app/components/ThemeToggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@ export default function ThemeToggle() {
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
setDark(isDark);
setMounted(true);

// Cross-tab sync: localStorage `storage` fires in OTHER tabs when this
// tab writes to theme. Mirror the change so two open tabs never disagree.
const onStorage = (e: StorageEvent) => {
if (e.key !== 'theme' || e.newValue == null) return;
const nextDark = e.newValue === 'dark';
document.documentElement.setAttribute('data-theme', nextDark ? 'dark' : 'light');
setDark(nextDark);
};
window.addEventListener('storage', onStorage);
return () => window.removeEventListener('storage', onStorage);
}, []);

const toggle = () => {
Expand Down
3 changes: 3 additions & 0 deletions app/hs/code/[subheadingCode]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { getCodeDetails, getSubheadingsByHeading, formatHsCode } from '@/lib/cal
import { HsSmallCard } from '@/app/hs/HsLinkCard';
import { getHsDgWarning, HS_DG_DISCLAIMER } from '@/lib/data/hs-dg-warnings';
import { buildHsCodeMetadata } from '@/lib/seo/page-metadata';
import NewsletterCapture from '@/app/components/NewsletterCapture';

export const dynamicParams = true;
export const revalidate = 86400;
Expand Down Expand Up @@ -192,6 +193,8 @@ export default async function SubheadingPage(
</a>
</p>

<NewsletterCapture />

</main>
</>
);
Expand Down
3 changes: 3 additions & 0 deletions app/hs/heading/[headingCode]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { getCodeDetails, getSubheadingsByHeading, formatHsCode } from '@/lib/cal
import HsLinkCard from '@/app/hs/HsLinkCard';
import { getHsDgWarning, HS_DG_DISCLAIMER } from '@/lib/data/hs-dg-warnings';
import { SITE_STATS } from '@/lib/constants/siteStats';
import NewsletterCapture from '@/app/components/NewsletterCapture';

export const dynamicParams = true;
export const revalidate = 86400;
Expand Down Expand Up @@ -128,6 +129,8 @@ export default async function HeadingPage(
</a>
</p>

<NewsletterCapture />

</main>
</>
);
Expand Down
2 changes: 1 addition & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const metadata: Metadata = {
metadataBase: new URL('https://www.freightutils.com'),
title: {
default: 'FreightUtils — Free Freight Calculators & APIs',
template: '%s | FreightUtils.com',
template: '%s | FreightUtils',
},
description:
'Free freight calculators with open REST APIs. Loading metres, CBM, chargeable weight, pallet fitting, and ADR dangerous goods lookup. No signup required.',
Expand Down
5 changes: 5 additions & 0 deletions lib/changelog-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ export interface ChangelogEntry {
}

export const entries: ChangelogEntry[] = [
{
isoDate: '2026-05-15', date: 'May 15', tag: 'Bug Fix',
title: 'Page consistency: titles, dark-mode sync, copy normalisation, HS email capture',
desc: 'Fixed double-branded titles (root template now appends "| FreightUtils" instead of "| FreightUtils.com"; /changelog uses title.absolute). Dark-mode toggle syncs across tabs via the storage event. /api-docs rate-limit copy normalised to "25 requests per day" across all three mentions. NewsletterCapture added to /hs/code/[code] and /hs/heading/[heading] templates so HS detail pages match the ADR-detail email-capture pattern.',
},
{
isoDate: '2026-05-14', date: 'May 14', tag: 'Security',
title: 'ScrapeGuard logs sanitised UA + full source IP on block decisions',
Expand Down
2 changes: 1 addition & 1 deletion lib/seo/page-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* - description must contain "free" AND ("no login" OR "updated {year}")
* - no template fallback returns just "FreightUtils" as the title
*
* The root layout's `title.template: '%s | FreightUtils.com'` would push
* The root layout's `title.template: '%s | FreightUtils'` would push
* every dynamic title past 60 chars, so the builders return
* `title: { absolute: ... }` to bypass the template. Index page metadata
* exports do the same for consistency.
Expand Down