Skip to content

Commit ec15d51

Browse files
committed
feat: change Vue router's mode from "hash" to "history" to support search engine indexing
In order to support loading a deep url without getting a 404 error, a custom 404.html page is implemented to redirect back to index.html which uses history api to load the original url without a refresh. Based on typeorm#36
1 parent 3e28369 commit ec15d51

File tree

5 files changed

+79
-24
lines changed

5 files changed

+79
-24
lines changed

404.html

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<!doctype html>
2+
<html lang="en" data-framework="typescript">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
6+
<script>
7+
// since the site is a single page application,
8+
// replace the current url to point to index.html with the entire path as a query property
9+
// so index.html could put the path back into the current url wia the history api
10+
window.location.replace(
11+
window.location.origin + "/?path=" + encodeURIComponent(window.location.href.substring(window.location.origin.length))
12+
);
13+
</script>
14+
15+
<link rel="apple-touch-icon" sizes="180x180" href="image/favicon/apple-touch-icon.png">
16+
<link rel="icon" type="image/png" sizes="32x32" href="image/favicon/favicon-32x32.png">
17+
<link rel="icon" type="image/png" sizes="16x16" href="image/favicon/favicon-16x16.png">
18+
<link rel="manifest" href="image/favicon/manifest.json">
19+
<link rel="mask-icon" href="image/favicon/safari-pinned-tab.svg" color="#666666">
20+
<meta name="theme-color" content="#ffffff">
21+
<title>
22+
TypeORM - Amazing ORM for TypeScript and JavaScript (ES7, ES6, ES5). Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, WebSQL databases. Works in NodeJS, Browser, Ionic, Cordova and Electron platforms.
23+
</title>
24+
</head>
25+
<body>
26+
</body>
27+
</html>

index.html

+8
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@
55
<meta charset="utf-8">
66
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
77

8+
<script>
9+
// if the current page was redirected from 404.html, put the path back in the url via the history api
10+
if (window.location.href.substring(window.location.origin.length).startsWith("/?path="))
11+
window.history.replaceState(null, document.title, window.location.origin + decodeURIComponent(
12+
window.location.href.substring(window.location.origin.length + "/?path=".length)
13+
));
14+
</script>
15+
816
<link rel="stylesheet"
917
href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css"
1018
integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB"

script/component/DocumentPage.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const DocumentPage = {
22
template: `
33
<div class="document">
4-
<markdown-reader :file="readUrl" :fragment="fragment"></markdown-reader>
4+
<markdown-reader :file="readUrl" :hash="hash"></markdown-reader>
55
66
<div class="contribute small">
77
{{$t("contribute")}} <a :href="editUrl">{{$t("edit")}}</a>
@@ -11,7 +11,7 @@ const DocumentPage = {
1111
data: function() {
1212
return {
1313
document: this.$route.params.document,
14-
fragment: this.$route.params.fragment,
14+
hash: this.$route.hash,
1515
locale: $cookies.get("locale") || "en",
1616
readLink: "https://raw.githubusercontent.com/typeorm/typeorm/master/",
1717
editLink: "https://github.com/typeorm/typeorm/edit/master/"
@@ -20,7 +20,7 @@ const DocumentPage = {
2020
watch: {
2121
'$route': function(to, from) {
2222
this.document = to.params.document;
23-
this.fragment = to.params.fragment;
23+
this.hash = to.hash;
2424
this.updateTitle();
2525
}
2626
},
@@ -58,4 +58,4 @@ const DocumentPage = {
5858
}
5959
}
6060
}
61-
};
61+
};

script/component/MarkdownReader.js

+18-14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const MarkdownReader = {
22
template: `<div v-html="html"></div>`,
3-
props: ["file", "fragment"],
3+
props: ["file", "hash"],
44
data: function() {
55
return {
66
html: "",
@@ -12,27 +12,31 @@ const MarkdownReader = {
1212
this.setDocument();
1313
this.loadFile(file)
1414
.then(() => {
15-
this.scrollToFragment(this.fragment);
15+
this.scrollToHash(this.hash);
1616
});
1717
},
18-
'fragment': function(fragment) {
19-
this.scrollToFragment(fragment);
18+
'hash': function(hash) {
19+
this.scrollToHash(hash);
2020
}
2121
},
2222
created: function () {
2323
this.setDocument();
2424
this.loadFile(this.file)
2525
.then(() => {
26-
this.scrollToFragment(this.fragment);
26+
this.scrollToHash(this.hash);
2727
});
2828
},
2929
methods: {
30-
scrollToFragment: function(fragment) {
31-
const fragmentElement = document.getElementById(fragment);
32-
if (fragmentElement)
33-
fragmentElement.scrollIntoView();
34-
else
35-
window.scrollTo(0, 0);
30+
scrollToHash: function(hash) {
31+
if (hash) {
32+
const fragmentElement = document.getElementById(hash.substring("#".length));
33+
if (fragmentElement) {
34+
fragmentElement.scrollIntoView();
35+
return;
36+
}
37+
}
38+
39+
window.scrollTo(0, 0);
3640
},
3741
setDocument: function () {
3842
if(this.$route.params.document) {
@@ -51,7 +55,7 @@ const MarkdownReader = {
5155

5256
showdown.extension('header-anchors', () => {
5357

54-
var ancTpl = '$1<a id="user-content-$3" class="anchor" href="#' + this.$route.params.document + '/$3" aria-hidden="true">#</a> $4';
58+
var ancTpl = '$1<a id="user-content-$3" class="anchor" href="#$3" aria-hidden="true">#</a> $4';
5559

5660
return [{
5761
type: "html",
@@ -64,15 +68,15 @@ const MarkdownReader = {
6468
return [{
6569
type: "html",
6670
regex: /<a href="#(.*)">/g,
67-
replace: "<a href='#/" + this.document + "/$1'>"
71+
replace: "<a href='/" + this.document + "#$1'>"
6872
}];
6973
});
7074

7175
showdown.extension('other-page-links-replacer', () => {
7276
return [{
7377
type: "html",
7478
regex: /<a href="\.?\/?(docs\/)?(.*?)\.md\#?(.*?)">/g,
75-
replace: "<a href='#/$2/$3'>"
79+
replace: "<a href='/$2#$3'>"
7680
}];
7781
});
7882

script/index.js

+22-6
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,30 @@ const i18n = new VueI18n({
1111
messages // set locale messages
1212
})
1313

14+
const router = new VueRouter({
15+
mode: "history",
16+
routes: [
17+
{ path: "/:document?/:fragment?", component: DocumentPage },
18+
]
19+
});
20+
21+
// Because the documentation previously used "hash" mode in the Router, paths
22+
// starting with a hash will be redirected to prevent breaking old permalinks.
23+
router.beforeEach((to, from, next) => {
24+
if (to.fullPath.startsWith("/#/")) {
25+
// Remove leading hash and replace following slash with hash.
26+
// Before: https://typeorm.io/#/entities/entity-columns
27+
// After: https://typeorm.io/entities#entity-columns
28+
const path = "/" + to.fullPath.substring("/#/".length).replace("/", "#");
29+
next(path);
30+
} else
31+
next();
32+
});
33+
1434
new Vue({
1535
el: "#app",
1636
i18n,
17-
router: new VueRouter({
18-
routes: [
19-
{ path: "/:document?/:fragment?", component: DocumentPage },
20-
]
21-
}),
37+
router: router,
2238
components: {
2339
"main-page": MainPage,
2440
"markdown-reader": MarkdownReader,
@@ -27,4 +43,4 @@ new Vue({
2743
i18n.locale = $cookies.get("locale") || "en";
2844
document.title = this.$t("title");
2945
}
30-
});
46+
});

0 commit comments

Comments
 (0)