Skip to content

fix(articles): 301 junk-tailed article URLs and 404 unknown slugs - #253

Merged
manager merged 1 commit into
mainfrom
release/article-junk-urls
Oct 15, 2026
Merged

manager merged 1 commit into
mainfrom
release/article-junk-urls

Conversation

@manager

@manager manager commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

Crawlers looped on article URLs whose encoded tail grew by one level per hop (slug%2529, slug%252529, ...). An unknown slug rendered as a 200 page because getCurrentArticle returns {} on a miss, so every variant became an ISR file on disk and its language toggle linked to the next variant.

  • middleware.ts: an /articles/<slug> path with characters outside [A-Za-z0-9_-] gets a 301 to the clean slug prefix.
  • src/pages/articles/[page].tsx: an unknown slug returns notFound (revalidate 10).

Checked: middleware run with NextRequest on junk and real paths (junk -> 301 clean slug, real slugs pass), tsc --noEmit clean.

Authorized by Wolf on 2026-10-15.

🤖 Generated with Claude Code

Crawlers looped on article URLs with an encoded tail that grew by one
encoding per hop (slug%2529, slug%252529, ...). Unknown slugs rendered
as 200 pages because getCurrentArticle returns {} on a miss, so every
variant became an ISR file and linked to the next one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Claude finished @manager's task in 17s —— View job


Review of PR #253

Verdict: looks good, no blocking issues.

middleware.ts

  • The redirect logic is sound. nextUrl.pathname stays percent-encoded, so slug%2529 fails ARTICLE_SLUG and is cut back to slug. A clean slug can't match the junk branch, so there is no redirect loop.
  • nextUrl.clone() keeps the query string and the locale. With i18n on, pathname has no locale prefix, so /articles/${slug} doesn't drop ru or hy. The optional locale group in ARTICLE_PATH is harmless but probably never needed.
  • A path with no valid slug prefix (for example /articles/%29) falls through to the page, which now returns a 404. That is the right behavior.
  • Risk to confirm: any real article slug with characters outside [A-Za-z0-9_-] (a dot, a non-ASCII character) would now be 301'd to a truncated slug and then 404. Please check the CMS slugs against that set before merging. Slugs with a dot are already skipped by the PUBLIC_FILE check.
  • Minor: /^[A-Za-z0-9_-]+/ is compiled on each call. You could hoist it next to the other constants.

src/pages/articles/[page].tsx

  • Checking Object.keys(article).length fixes the case where getCurrentArticle returns {}. revalidate: 10 on notFound stops junk slugs from being cached as 404s for good, and lets a newly published article appear quickly.
  • Confirm that getStaticPaths uses fallback: 'blocking'. Otherwise unknown slugs never reach getStaticProps.

Conventions: no App Router, styling, or UX Core data changes. I didn't run the tests. The PR description says tsc was clean and the middleware was checked with NextRequest.

Existing junk-tail ISR files already on disk will clear as they revalidate to 404.

@manager

manager commented Oct 15, 2026

Copy link
Copy Markdown
Contributor Author

Both points checked:

  • Slugs: every production article slug matches [a-z0-9_-] (63 rows checked against the CMS). One row holds an external URL in place of a slug; it contains /, so no article route ever matched it, and that is unchanged.
  • getStaticPaths returns fallback: 'blocking', so unknown slugs reach getStaticProps.
  • Leaving the inline regex as is: it runs only on article paths with a junk tail.

@manager
manager merged commit eaf2c2a into main Oct 15, 2026
2 checks passed
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.

1 participant