Skip to content

Commit 17f880e

Browse files
authored
feat: seo improvements (#1013)
* feat: add canonical url for posts * refactor: cleanup * feat: show reading time to google * feat: rearrange code for more efficient loading of posts
1 parent e8d353e commit 17f880e

File tree

2 files changed

+60
-37
lines changed

2 files changed

+60
-37
lines changed

nuxt.config.ts

+1
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ const config: NuxtConfig = {
155155
ignoreOrder: false,
156156
},
157157
extend(conf, _) {
158+
// @ts-ignore
158159
conf.resolve.alias.vue = `vue/dist/vue.common`
159160
},
160161
splitChunks: {

src/pages/post/_post.vue

+59-37
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,7 @@ interface IData {
365365
timestamp: number | null
366366
excerpt: string | null
367367
category: string | null
368+
friendlyUrl: string | null
368369
encrypted: boolean
369370
deleted: boolean
370371
tags: Tag[]
@@ -387,14 +388,15 @@ interface IData {
387388
captionHeight?: number
388389
showShare: boolean
389390
readingTime: number | null
391+
wordcount: number | null
392+
postImages: Array<string>
390393
realURL: string
391394
isLeaving: boolean
392395
showPaywall: boolean
393396
showSubscriptions: boolean
394397
enabledTiers: Array<string>
395398
subscriptionStatus: `INSUFFICIENT_TIER` | `NOT_SUBSCRIBED` | ``
396399
postImageKeys: Array<IPostImageKey>
397-
isMetadataLoading: boolean
398400
isContentLoading: boolean
399401
}
400402
@@ -424,7 +426,6 @@ export default Vue.extend({
424426
next()
425427
},
426428
layout: `reader`,
427-
// mixins: [markdown],
428429
data(): IData {
429430
return {
430431
title: null,
@@ -435,6 +436,9 @@ export default Vue.extend({
435436
category: null,
436437
deleted: false,
437438
encrypted: false,
439+
friendlyUrl: null,
440+
postImages: [],
441+
wordcount: null,
438442
tags: [],
439443
post: null,
440444
author: null,
@@ -463,17 +467,18 @@ export default Vue.extend({
463467
enabledTiers: [],
464468
subscriptionStatus: ``,
465469
postImageKeys: [],
466-
isMetadataLoading: true,
467470
isContentLoading: true,
468471
}
469472
},
470473
head() {
474+
const canonicalLink = {
475+
rel: `canonical`,
476+
// @ts-ignore
477+
href: this.friendlyUrl,
478+
}
471479
return {
472480
// @ts-ignore
473-
title: this.title
474-
? // @ts-ignore
475-
`${this.title} by ${this.authorID} on Blogchain`
476-
: `Loading...`,
481+
title: this.title ?? `Loading...`,
477482
meta: [
478483
{
479484
hid: `description`,
@@ -482,6 +487,10 @@ export default Vue.extend({
482487
content: this.subtitle ?? this.excerpt,
483488
},
484489
],
490+
link: [
491+
// @ts-ignore
492+
...(this.friendlyUrl ? [canonicalLink] : []),
493+
],
485494
}
486495
},
487496
beforeDestroy() {
@@ -507,13 +516,22 @@ export default Vue.extend({
507516
508517
this.updatePostMetadata(postData)
509518
this.excerpt = postData.excerpt
519+
// Get reading time
520+
this.calculateReadingTime()
510521
511-
// Get featured photo
512-
if (postData.featuredPhotoCID) {
513-
getPhotoFromIPFS(postData.featuredPhotoCID).then((p) => {
514-
this.featuredPhoto = p
522+
// Change URL to social-friendly link, preserve real for Vue router
523+
createShareableLink(this.$route.params.post)
524+
.then((friendlyUrl) => {
525+
this.friendlyUrl = friendlyUrl
526+
if (!this.isLeaving) {
527+
this.realURL = this.$route.fullPath
528+
history.replaceState(null, ``, friendlyUrl)
529+
}
530+
})
531+
.catch((err) => {
532+
// eslint-disable-next-line no-console
533+
console.log(`Cannot replace state to shareable link: ${err.message}`)
515534
})
516-
}
517535
518536
// Get author profile
519537
this.author = createDefaultProfile(postData.authorID)
@@ -529,31 +547,17 @@ export default Vue.extend({
529547
}
530548
})
531549
532-
// Change URL to social-friendly link, preserve real for Vue router
533-
createShareableLink(this.$route.params.post)
534-
.then((friendlyUrl) => {
535-
if (!this.isLeaving) {
536-
this.realURL = this.$route.fullPath
537-
history.replaceState(null, ``, friendlyUrl)
538-
}
539-
})
540-
.catch((err) => {
541-
// eslint-disable-next-line no-console
542-
console.log(`Cannot replace state to shareable link: ${err.message}`)
550+
// Get featured photo
551+
if (postData.featuredPhotoCID) {
552+
getPhotoFromIPFS(postData.featuredPhotoCID).then((p) => {
553+
this.featuredPhoto = p
543554
})
555+
}
544556
545557
// Unauthenticated
546558
if (sessionID === ``) {
547559
return
548560
}
549-
550-
try {
551-
await this.fetchPaymentProfile({ username: this.authorID })
552-
} catch (err) {
553-
if (!(err instanceof AxiosError && err.response?.status === 404)) {
554-
this.$handleError(err)
555-
}
556-
}
557561
} catch (err: unknown) {
558562
if (!(err instanceof Error)) {
559563
throw err
@@ -563,8 +567,6 @@ export default Vue.extend({
563567
}
564568
565569
this.$toastError(err.message)
566-
} finally {
567-
this.isMetadataLoading = false
568570
}
569571
570572
// This is a new post
@@ -581,6 +583,8 @@ export default Vue.extend({
581583
const postCID = this.$route.params.post
582584
const sessionID = this.$store.state.session.id
583585
try {
586+
// Uncomment this to test the loading behaviour/what google sees
587+
// await new Promise((resolve) => setTimeout(resolve, 1000 * 1000))
584588
const post = await getPost(postCID)
585589
verifyPostAuthenticity(post.data, post.sig, post.public_key).then((verified) => {
586590
if (!verified) {
@@ -590,6 +594,13 @@ export default Vue.extend({
590594
591595
const postData = post.data
592596
this.updatePostMetadata(postData)
597+
598+
this.fetchPaymentProfile({ username: this.authorID }).catch((err) => {
599+
if (!(err instanceof AxiosError && err.response?.status === 404)) {
600+
this.$handleError(err)
601+
}
602+
})
603+
593604
// Get featured photo
594605
if (postData.featuredPhotoCID && !this.featuredPhoto) {
595606
getPhotoFromIPFS(postData.featuredPhotoCID).then((p) => {
@@ -682,6 +693,8 @@ export default Vue.extend({
682693
category: string
683694
tags: Tag[]
684695
encrypted?: boolean
696+
wordCount?: number
697+
postImages?: Array<string>
685698
}) {
686699
this.title = postData.title
687700
if (postData.subtitle) {
@@ -693,6 +706,12 @@ export default Vue.extend({
693706
if (postData.encrypted) {
694707
this.encrypted = postData.encrypted
695708
}
709+
if (postData.wordCount) {
710+
this.wordcount = postData.wordCount
711+
}
712+
if (postData.postImages) {
713+
this.postImages = postData.postImages
714+
}
696715
this.tags = postData.tags
697716
},
698717
async getBookmarkStatus() {
@@ -784,14 +803,17 @@ export default Vue.extend({
784803
})
785804
},
786805
calculateReadingTime() {
787-
if (!this.post) {
788-
throw new Error(`Post can't be null`)
806+
let wordcount = this.wordcount
807+
if (this.content) {
808+
wordcount = this.content.split(/\s+/).length
809+
}
810+
if (!wordcount) {
811+
return
789812
}
790-
const wordcount = this.content.split(/\s+/).length
791813
if (wordcount <= 0) {
792814
throw new Error(`Word count can't be equal or less than zero`)
793815
}
794-
this.readingTime = calculateReadingTime(wordcount, this.post.postImages?.length)
816+
this.readingTime = calculateReadingTime(wordcount, this.postImages.length)
795817
},
796818
toggleSubscription() {
797819
// Unauth

0 commit comments

Comments
 (0)