Project page fails to load due to read_more.js SyntaxError when anchor links to multibyte README heading

Everyone can contribute. Help move this issue forward while earning points, leveling up and collecting rewards.

Summary

When an anchor link points to a header in the project README preview that contains multibyte characters (e.g., Japanese), the GitLab project page fails to complete rendering.

This is due to an uncaught SyntaxError in read_more.js.

Steps to reproduce

  1. Create a project with a project description and a README that includes a multibyte-character heading, such as ## 日本語.
  2. On the project’s overview page, click the anchor link in the README preview that points to that heading.
  3. Observe that the page gets stuck in a loading state and does not render the content.

Example Project

https://gitlab.com/monkey36/check-readme-anchor-error

What is the current bug behavior?

The page fails to load completely after clicking the anchor link. In the browser's developer console, the following error appears:

read_more.js:43  Uncaught SyntaxError: Failed to execute 'querySelector' on 'Element': '#user-content-%E6%97%A5%E6%9C%AC%E8%AA%9E' is not a valid selector.
    at read_more.js:43:46
    at NodeList.forEach (<anonymous>)
    at o (read_more.js:27:14)
    at Module.tGlJ (index.js:9:1)
    at r (bootstrap:101:22)
    at 324 (pages.projects.show.1baaf8d8.chunk.js:1:2259)
    at r (bootstrap:101:22)
    at c (bootstrap:45:15)
    at Array.a [as push] (bootstrap:32:11)
    at pages.projects.show.1baaf8d8.chunk.js:1:43

What is the expected correct behavior?

The anchor should correctly scroll to the specified section, and the page should load normally, even if the heading contains multibyte characters like Japanese.

Relevant logs and/or screenshots

readme_load_error_2025-08-03_002043

Output of checks

This bug happens on GitLab.com

Implementation guide

In read_more.js, the following line causes the issue:

const hashTargetEl = readMoreContent.querySelector(`#user-content-${targetId}`);

https://gitlab.com/gitlab-org/gitlab/-/blob/3064a0b660e9feec069ac4bcba46cfdb64549459/app/assets/javascripts/read_more.js#L43

This could be fixed by using CSS.escape():

const hashTargetEl = readMoreContent.querySelector(`#user-content-${CSS.escape(targetId)}`);

This would ensure valid CSS selectors even when the id contains multibyte or special characters.

I'm still a beginner in JavaScript, so I haven't tested this fix myself—apologies if it’s incorrect, but I hope it helps point in the right direction.

Edited by 🤖 GitLab Bot 🤖