Repository: vuepress/vuepress-next (ts)

Issue:
[Bug report] Page flickers after initial load
<!-- Please fill in relevant information as much as possible. That would be helpful to locate the problem and save us each other's time -->

<!-- If some fields are irrelevant or you are not sure, remove it or mark it as "not related" / not sure" -->

<!-- If you don't follow the template and not providing valid information, we'll close your issue without further reply -->

## Bug report

### Description

<!-- A clear and concise description of what the bug is. -->

When the page loads, it first displays the frontpage, then switches to an all white screen, and then displays it correctly again. This flickering takes less than a second in both Chrome and Firefox with no cache, and gets faster on subsequent page reloads. This affects only the front page.

### Steps to reproduce

<!-- Steps to reproduce the behavior -->

- Reproduction link / repo:

  Look at <https://grammy.dev/> which is built from [here](https://github.com/grammyjs/website/tree/main/site) using `"vuepress": "^2.0.0-beta.9"`.

In fact, you can also observe it on the documentation of Vuepress Next itself at <https://vuepress2.netlify.app/> if you throttle your network in the devtools, e.g. to Slow 3G.

This does not happen for Vuepress itself, <https://vuepress.vuejs.org/> works just fine under all circumstances.

### Expected behavior

<!-- A clear and concise description of what you expected to happen. -->

When the page loads, the content does not disappear again after being displayed.

### Screenshots

<!-- If applicable, add screenshots to help explain your problem. -->

![rec-2021-04-25_13 52 14 (1)](https://user-images.githubusercontent.com/12952387/115992956-9d8d5d00-a5d0-11eb-9df6-b09690f2acb3.gif)

### Environment info

- Browser: Chrome (Linux), Chrome (Android), Firefox ESR (Linux)
- Output of `vuepress info`:

```bash
$ npx vuepress info

  System:
    OS: Linux 4.19 Debian GNU/Linux 10 (buster) 10 (buster)
    CPU: (4) x64 Intel(R) Core(TM) i5-4200M CPU @ 2.50GHz
    Memory: 2.59 GB / 7.55 GB
    Shell: 5.0.3 - /bin/bash
  Binaries:
    Node: 14.16.1 - /usr/bin/node
    Yarn: 1.22.10 - /usr/local/bin/yarn
    npm: 7.6.1 - /usr/local/bin/npm
  Utilities:
    Git: 2.20.1 - /usr/bin/git
  Browsers:
    Chrome: 90.0.4430.85
    Firefox: 78.10.0esr
  npmPackages:
    @vuepress/bundler-vite: Not Found
    @vuepress/bundler-webpack:  2.0.0-beta.9 
    @vuepress/cli:  2.0.0-beta.9 
    @vuepress/client:  2.0.0-beta.9 
    @vuepress/core:  2.0.0-beta.9 
    @vuepress/markdown:  2.0.0-beta.9 
    @vuepress/plugin-active-header-links:  2.0.0-beta.9 
    @vuepress/plugin-back-to-top:  2.0.0-beta.9 
    @vuepress/plugin-container:  2.0.0-beta.9 
    @vuepress/plugin-debug: Not Found
    @vuepress/plugin-docsearch: ^2.0.0-beta.9 => 2.0.0-beta.9 
    @vuepress/plugin-git:  2.0.0-beta.9 
    @vuepress/plugin-google-analytics: Not Found
    @vuepress/plugin-medium-zoom:  2.0.0-beta.9 
    @vuepress/plugin-nprogress:  2.0.0-beta.9 
    @vuepress/plugin-palette:  2.0.0-beta.9 
    @vuepress/plugin-pwa: Not Found
    @vuepress/plugin-pwa-popup: Not Found
    @vuepress/plugin-theme-data:  2.0.0-beta.9 
    @vuepress/shared:  2.0.0-beta.7 
    @vuepress/theme-default:  2.0.0-beta.9 
    @vuepress/theme-vue: Not Found
    @vuepress/utils:  2.0.0-beta.8 
    vue:  3.0.11 
    vue-loader:  16.2.0 
    vue-router:  4.0.6 
    vuepress: ^2.0.0-beta.9 => 2.0.0-beta.9 
    vuepress-vite: Not Found
```

[Bug report] The header anchor should stay outside of the heading tag
### Description

The header anchors `#` are placed inside the heading tags (h1, h2, h3...).
This behaviour is not actually correct, since the heading tag should explicitly indicate the nature of the content as a title, and `#` shouldn't be part of it.

<img width="597" alt="Screenshot 2023-06-15 at 00 29 02" src="https://github.com/vuepress/vuepress-next/assets/13590581/bd3967a7-4556-4bce-b3d5-9a10ba43b3f4">

Apart from the semantic issue, there is also a more problematic issue: crawlers, some social media crawler may take the title from the h1 heading tag to create a post preview. This means that the preview title of the post will contain the "#" character.

This is an example of a VuePress post preview on LinkedIn

<img width="797" alt="Screenshot 2023-06-15 at 00 34 02" src="https://github.com/vuepress/vuepress-next/assets/13590581/40f04bbf-66fc-48a2-8303-d7a53865114d">

To reproduce I have a live link
https://www.mida.org/posts/reactive-programming-in-financial-markets/

The website above is built with VuePress 1 but the issue persists also on VuePress 2.

Thank you,
Vasile

### Reproduction

https://www.mida.org/posts/reactive-programming-in-financial-markets/

### Used Package Manager

npm

### System Info

```shell
Not relevant
```


Pull request:
feat(markdown): update default markdown-it-anchor permalink function (close #1363)
<!-- Thank you for contributing! -->

### Before submitting the PR, please make sure you do the following <!-- (put an "X" next to an item) -->

- [X] Read the [Contributing Guidelines](https://github.com/vuepress/vuepress-next/blob/main/CONTRIBUTING.md).
- [X] Provide a description in this PR that addresses **what** the PR is solving. If this PR is going to solve an existing issue, please reference the issue (e.g. `close #123`).

### What is the purpose of this pull request? <!-- (put an "X" next to an item) -->

- [X] Bug fix
- [ ] New feature
- [ ] Documentation update
- [ ] Other

### Description

The heading anchors have `aria-hidden` attributes but are still focusable via keyboard navigation.  There was a bit of discussion around this already in the [`markdown-it-anchor` repo](https://github.com/valeriangalliat/markdown-it-anchor/issues/82) and you can address this by just using the `headerLink` render function instead of the `ariaHidden` render function since it wraps the entire header in an anchor.

This fixes #1363 

### Screenshots

We will also need to make some changes to `theme-default`'s base styles to accompany these changes. I will put that PR up shortly and include screenshots there.

**Before**

**After**


Patch:
diff --git a/packages/markdown/src/markdown.ts b/packages/markdown/src/markdown.ts
index 715c30ce..abe12352 100644
--- a/packages/markdown/src/markdown.ts
+++ b/packages/markdown/src/markdown.ts
@@ -63,11 +63,10 @@ export const createMarkdown = ({
     md.use<AnchorPluginOptions>(anchorPlugin, {
       level: [1, 2, 3, 4, 5, 6],
       slugify,
-      permalink: anchorPlugin.permalink.ariaHidden({
+      permalink: anchorPlugin.permalink.headerLink({
         class: 'header-anchor',
-        symbol: '#',
-        space: true,
-        placement: 'before',
+        // Add a span inside the link so Safari shows headings in reader view.
+        safariReaderFix: true,
       }),
       ...anchor,
     })


Test patch:
diff --git a/e2e/docs/markdown/anchors.md b/e2e/docs/markdown/anchors.md
new file mode 100644
index 00000000..9769c893
--- /dev/null
+++ b/e2e/docs/markdown/anchors.md
@@ -0,0 +1,5 @@
+# title
+
+## anchor 1
+
+### anchor 1-1
diff --git a/e2e/tests/markdown/anchors.cy.ts b/e2e/tests/markdown/anchors.cy.ts
new file mode 100644
index 00000000..d9f7fc93
--- /dev/null
+++ b/e2e/tests/markdown/anchors.cy.ts
@@ -0,0 +1,26 @@
+describe('markdown > anchors', () => {
+  it('should render anchors and navigate correctly', () => {
+    cy.visit('/markdown/anchors.html')
+
+    cy.get('.e2e-theme-content h1')
+      .should('have.attr', 'id', 'title')
+      .should('have.attr', 'tabindex', '-1')
+
+    cy.get('.e2e-theme-content h1 > a')
+      .should('have.attr', 'class', 'header-anchor')
+      .should('have.attr', 'href', '#title')
+      .click()
+
+    cy.location().should((location) => {
+      expect(location.hash).to.eq('#title')
+    })
+
+    cy.get('#anchor-1-1 > a')
+      .should('have.attr', 'class', 'header-anchor')
+      .click()
+
+    cy.location().should((location) => {
+      expect(location.hash).to.eq('#anchor-1-1')
+    })
+  })
+})
diff --git a/packages/markdown/tests/markdown.spec.ts b/packages/markdown/tests/markdown.spec.ts
index 674b47c4..ec5ddfff 100644
--- a/packages/markdown/tests/markdown.spec.ts
+++ b/packages/markdown/tests/markdown.spec.ts
@@ -2,22 +2,25 @@ import { describe, expect, it } from 'vitest'
 import { createMarkdown } from '../src/index.js'
 
 describe('@vuepress/markdown > markdown', () => {
-  describe('options', () => {
-    it.todo('anchor')
+  const md = createMarkdown()
 
-    it.todo('emoji')
-
-    it.todo('links')
+  it('should render anchors', () => {
+    const rendered = md.render(`\
+# h1
+## h2
+### h3
+`)
+    expect(rendered).toEqual(
+      [
+        '<h1 id="h1" tabindex="-1"><a class="header-anchor" href="#h1"><span>h1</span></a></h1>',
+        '<h2 id="h2" tabindex="-1"><a class="header-anchor" href="#h2"><span>h2</span></a></h2>',
+        '<h3 id="h3" tabindex="-1"><a class="header-anchor" href="#h3"><span>h3</span></a></h3>',
+      ].join('\n') + '\n',
+    )
   })
 
-  describe('e2e', () => {
-    const md = createMarkdown()
-
-    it.todo('anchor')
-
-    it('should parse emoji', () => {
-      const rendered = md.render(':smile:')
-      expect(rendered).toBe('<p>😄</p>\n')
-    })
+  it('should parse emoji', () => {
+    const rendered = md.render(':smile:')
+    expect(rendered).toBe('<p>😄</p>\n')
   })
 })