Skip to content

Fix uppercase content filenames and enforce lowercase via Tina schema#4641

Open
isaaclombardssw wants to merge 4 commits intomainfrom
fix/uppercase-url-paths-4482
Open

Fix uppercase content filenames and enforce lowercase via Tina schema#4641
isaaclombardssw wants to merge 4 commits intomainfrom
fix/uppercase-url-paths-4482

Conversation

@isaaclombardssw
Copy link
Copy Markdown
Member

@isaaclombardssw isaaclombardssw commented Apr 10, 2026

Summary

Fixes #4482 — Uppercase URL paths fail to load due to Cloudflare's lowercase transform rule conflicting with mixed-case content filenames.

Root cause: Cloudflare has a transform rule that lowercases all URI paths. Some Tina CMS content files had uppercase characters in their filenames, causing a mismatch when Cloudflare's lowercase URL didn't match the original case on disk.

Fix approach (pivoted from middleware redirect):

  • Renamed 971 content files with uppercase characters to lowercase
  • Updated all cross-references in content files to match
  • Added kebabCaseFilename to all remaining Tina collections that were missing it (technologies, testimonialCategories, presenter, events-calendar, opportunities, newsletters, payment-details) — prevents future uppercase filenames at creation time
  • Added a Jest test that fails if any content file has uppercase characters in its filename

Note: Tina collection directory names with camelCase (e.g. testimonialCategories, v2Technologies) were intentionally left as-is — they are Tina collection identifiers tied to the schema name and path fields, and renaming them would require updating GraphQL queries and TinaCloud's API.

Action item: The Cloudflare "lowercase all paths" transform rule can now be safely left in place, or optionally removed since all content is now lowercase.

Test plan

  • pnpm lint passes
  • Jest test __tests__/content-filenames.test.ts passes (no uppercase content filenames)
  • Verify site loads correctly on PR preview (content pages render with correct data)
  • Verify Tina CMS editor still works with renamed content

🤖 Generated with Claude Code

Issue #4482: pages with uppercase characters in the URL path were
returning a 200 not-found fallback at the Next.js layer. Cloudflare
was masking this in production by force-lowercasing all paths, which
in turn was breaking mixed-case Tina filenames such as
/consulting/TenderPortals.

Middleware now lowercases the FIRST path segment only when it contains
uppercase characters (308 redirect, query string preserved), leaving
deeper segments untouched so mixed-case Tina routes keep working.
Static asset paths, /_next/, and /api/ are skipped.

Adds Playwright regression coverage in ui-tests/uppercase-redirect.spec.ts.

Note: the Cloudflare lowercase transformation rule must also be removed
or narrowed for this fix to fully take effect in production.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 10, 2026 05:05
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 10, 2026

Coverage report

St.
Category Percentage Covered / Total
🔴 Statements 0.19% 72/37368
🔴 Branches 5.61% 24/428
🔴 Functions 0.74% 3/407
🔴 Lines 0.19% 72/37368

Test suite run success

13 tests passing in 1 suite.

Report generated by 🧪jest coverage report action from 9b8d8e4

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a Next.js middleware redirect to normalize uppercase characters in only the first URL path segment, fixing origin behavior where /Consulting, /Company, etc. fall into a 200 “not found shell” instead of routing correctly, while preserving mixed-case deep segments needed for Tina/consultingv2 filenames (e.g. /consulting/TenderPortals).

Changes:

  • Implement first-segment-only lowercase normalization in middleware.ts with a 308 redirect, skipping /_next/, /api/, and likely-static-file paths.
  • Add Playwright coverage for uppercase redirects and mixed-case deep-segment preservation in ui-tests/uppercase-redirect.spec.ts.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
middleware.ts Adds first-segment lowercase redirect logic via middleware.
ui-tests/uppercase-redirect.spec.ts Introduces Playwright regression tests for redirect + deep-segment preservation behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread middleware.ts Outdated
Comment on lines 23 to 28
return NextResponse.redirect(url, 308);
}
}

const response = NextResponse.next();

Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The early return on redirect skips the existing header logic (HSTS and X-Robots-Tag / noindex via addNoIndexHeaders). If you rely on these headers being present on all responses (including redirects), build the redirect response first and then apply the same header mutations before returning it (or refactor header-setting into a shared helper used by both redirect and NextResponse.next()).

Suggested change
return NextResponse.redirect(url, 308);
}
}
const response = NextResponse.next();
const response = NextResponse.redirect(url, 308);
return applyResponseHeaders(request, response);
}
}
return applyResponseHeaders(request, NextResponse.next());
}
function applyResponseHeaders(
request: NextRequest,
response: NextResponse,
): NextResponse {

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — applied. Refactored header-setting into a shared applySharedHeaders helper and it now runs on both the 308 redirect and the pass-through NextResponse.next(). The noindex header in particular matters here, because a crawler that indexes the 308 itself (without following) on a PR/staging host would otherwise let the preview leak into search engines. Verified locally — the 308 response now carries strict-transport-security and x-robots-tag. Fixed in 9b8d8e4.

Comment thread ui-tests/uppercase-redirect.spec.ts Outdated
Comment on lines +1 to +13
import { expect, test } from "@playwright/test";

test("🔠 Uppercase top-level path is redirected (308) to lowercase", async ({
request,
baseURL,
}) => {
const response = await request.get(`${baseURL}/Consulting`, {
maxRedirects: 0,
failOnStatusCode: false,
});
expect(response.status()).toBe(308);
expect(response.headers()["location"]).toMatch(/\/consulting$/);
});
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new spec won’t run in the current CI workflows because the Playwright job filters tests via tests_to_run (e.g. images seo-noindex), which won’t match uppercase-redirect. To make this regression coverage effective, update the workflow inputs to include this spec (or adjust the selection strategy) so it runs on PR/staging deployments.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified and fixed — template-ui-tests.yml runs npx playwright test ${{ inputs.tests_to_run }} which uses the input as a positional test-file filter, so uppercase-redirect wouldn't match. Added it to the tests_to_run inputs in both pr-push-deploy.yml and main-build-and-deploy.yml. Fixed in 9b8d8e4.

@github-actions
Copy link
Copy Markdown
Contributor

Deployed changes to https://app-sswwebsite-9eb3-pr-4641.azurewebsites.net

@github-actions
Copy link
Copy Markdown
Contributor

🚀 Lighthouse score comparison for PR slot and production

🌐 URL ⚡ Performance ♿ Accessibility ✅ Best Practices 🔍 SEO 📦 Bundle Size 🗑️ Unused Bundle
https://app-sswwebsite-9eb3-pr-4641.azurewebsites.net/ 63 (⬇️6) 91 59 69 (⬇️31) 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4641.azurewebsites.net/articles 65 (⬇️8) 96 59 57 (⬇️35) 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4641.azurewebsites.net/company/about-us 48 (⬇️3) 100 59 69 (⬇️31) 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4641.azurewebsites.net/company/clients 65 (⬇️7) 95 59 69 (⬇️31) 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4641.azurewebsites.net/company/contact-us 63 (⬇️6) 91 59 61 (⬇️31) 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4641.azurewebsites.net/consulting 51 (⬇️10) 91 56 69 (⬇️31) 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4641.azurewebsites.net/consulting/net-upgrade 34 (⬇️5) 89 56 54 (⬇️31) 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4641.azurewebsites.net/consulting/web-applications 44 (⬆️11) 93 59 54 (⬇️31) 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4641.azurewebsites.net/employment 59 (⬆️3) 95 59 69 (⬇️31) 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4641.azurewebsites.net/events/angular-superpowers-tour 62 (⬇️3) 96 56 69 (⬇️31) 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4641.azurewebsites.net/events/ai-workshop 64 (⬇️4) 90 56 61 (⬇️31) 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4641.azurewebsites.net/netug/sydney 54 (⬇️3) 93 56 61 (⬇️31) 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4641.azurewebsites.net/training/internship-fullstack 64 (⬆️5) 91 59 61 (⬇️31) 0.00 MB 0.00 MB

…n CI

Apply HSTS, X-Robots-Tag, and noindex headers to the 308 redirect response
(not just the pass-through), so PR/staging previews don't leak indexable
redirects to crawlers that don't follow them.

Include uppercase-redirect in the tests_to_run filter for both the PR
deploy and main build workflows, since the workflow passes positional
args to playwright test and would otherwise skip the new spec.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

Deployed changes to https://app-sswwebsite-9eb3-pr-4641.azurewebsites.net

@github-actions
Copy link
Copy Markdown
Contributor

🚀 Lighthouse score comparison for PR slot and production

🌐 URL ⚡ Performance ♿ Accessibility ✅ Best Practices 🔍 SEO 📦 Bundle Size 🗑️ Unused Bundle
https://app-sswwebsite-9eb3-pr-4641.azurewebsites.net/ 31 (⬇️38) 96 (⬆️5) 56 (⬇️3) 69 (⬇️31) 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4641.azurewebsites.net/articles 60 (⬇️13) 96 59 57 (⬇️35) 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4641.azurewebsites.net/company/about-us 56 (⬆️5) 100 59 69 (⬇️31) 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4641.azurewebsites.net/company/clients 51 (⬇️21) 95 59 69 (⬇️31) 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4641.azurewebsites.net/company/contact-us 60 (⬇️9) 91 59 61 (⬇️31) 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4641.azurewebsites.net/consulting 47 (⬇️14) 91 56 69 (⬇️31) 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4641.azurewebsites.net/consulting/net-upgrade 34 (⬇️5) 89 56 54 (⬇️31) 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4641.azurewebsites.net/consulting/web-applications 34 (⬆️1) 93 59 54 (⬇️31) 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4641.azurewebsites.net/employment 53 (⬇️3) 95 59 69 (⬇️31) 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4641.azurewebsites.net/events/angular-superpowers-tour 56 (⬇️9) 96 56 69 (⬇️31) 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4641.azurewebsites.net/events/ai-workshop 56 (⬇️12) 90 56 61 (⬇️31) 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4641.azurewebsites.net/netug/sydney 81 (⬆️24) 93 56 61 (⬇️31) 0.00 MB 0.00 MB
https://app-sswwebsite-9eb3-pr-4641.azurewebsites.net/training/internship-fullstack 61 (⬆️2) 91 59 61 (⬇️31) 0.00 MB 0.00 MB

…a Tina schema

Instead of redirecting uppercase URLs in middleware, fix the root cause:
- Rename 971 content files with uppercase characters to lowercase
- Update all cross-references in content files to match new filenames
- Add kebabCaseFilename to all remaining Tina collections (technologies,
  testimonialCategories, presenter, events-calendar, opportunities,
  newsletters, payment-details)
- Add Jest test to prevent future uppercase content filenames
- Revert middleware redirect logic and workflow changes from prior approach

This allows the Cloudflare lowercase transform rule to work correctly
without breaking any content routes.

Closes #4482

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@isaaclombardssw isaaclombardssw changed the title Fix #4482: Normalize uppercase URL paths via middleware redirect Fix uppercase content filenames and enforce lowercase via Tina schema Apr 14, 2026
…ement

The existing parse function alone was not preventing uppercase filenames
in the Tina UI. Adding slugify auto-generates lowercase kebab-case
filenames from the title/name field on document creation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🐛 Bug - Uppercase URL paths fail to load due to forced lowercasing

2 participants