aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/lib
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2019-02-21 14:06:10 +0100
committerChocobozzz <me@florianbigard.com>2019-02-21 14:06:10 +0100
commit92bf2f62995bbaa0402cb4657473ad8d5b6fcf8d (patch)
tree7f3f34b1503fd21db7f0b913c3b908f004015011 /server/lib
parent84c7cde6e81426a42e7aa29187b473bc89f1c8f6 (diff)
downloadPeerTube-92bf2f62995bbaa0402cb4657473ad8d5b6fcf8d.tar.gz
PeerTube-92bf2f62995bbaa0402cb4657473ad8d5b6fcf8d.tar.zst
PeerTube-92bf2f62995bbaa0402cb4657473ad8d5b6fcf8d.zip
Improve channel and account SEO
Diffstat (limited to 'server/lib')
-rw-r--r--server/lib/client-html.ts67
1 files changed, 53 insertions, 14 deletions
diff --git a/server/lib/client-html.ts b/server/lib/client-html.ts
index b2c376e20..217f6a437 100644
--- a/server/lib/client-html.ts
+++ b/server/lib/client-html.ts
@@ -1,5 +1,4 @@
1import * as express from 'express' 1import * as express from 'express'
2import * as Bluebird from 'bluebird'
3import { buildFileLocale, getDefaultLocale, is18nLocale, POSSIBLE_LOCALES } from '../../shared/models/i18n/i18n' 2import { buildFileLocale, getDefaultLocale, is18nLocale, POSSIBLE_LOCALES } from '../../shared/models/i18n/i18n'
4import { CONFIG, CUSTOM_HTML_TAG_COMMENTS, EMBED_SIZE } from '../initializers' 3import { CONFIG, CUSTOM_HTML_TAG_COMMENTS, EMBED_SIZE } from '../initializers'
5import { join } from 'path' 4import { join } from 'path'
@@ -9,10 +8,13 @@ import * as validator from 'validator'
9import { VideoPrivacy } from '../../shared/models/videos' 8import { VideoPrivacy } from '../../shared/models/videos'
10import { readFile } from 'fs-extra' 9import { readFile } from 'fs-extra'
11import { getActivityStreamDuration } from '../models/video/video-format-utils' 10import { getActivityStreamDuration } from '../models/video/video-format-utils'
11import { AccountModel } from '../models/account/account'
12import { VideoChannelModel } from '../models/video/video-channel'
13import * as Bluebird from 'bluebird'
12 14
13export class ClientHtml { 15export class ClientHtml {
14 16
15 private static htmlCache: { [path: string]: string } = {} 17 private static htmlCache: { [ path: string ]: string } = {}
16 18
17 static invalidCache () { 19 static invalidCache () {
18 ClientHtml.htmlCache = {} 20 ClientHtml.htmlCache = {}
@@ -28,18 +30,14 @@ export class ClientHtml {
28 } 30 }
29 31
30 static async getWatchHTMLPage (videoId: string, req: express.Request, res: express.Response) { 32 static async getWatchHTMLPage (videoId: string, req: express.Request, res: express.Response) {
31 let videoPromise: Bluebird<VideoModel>
32
33 // Let Angular application handle errors 33 // Let Angular application handle errors
34 if (validator.isInt(videoId) || validator.isUUID(videoId, 4)) { 34 if (!validator.isInt(videoId) && !validator.isUUID(videoId, 4)) {
35 videoPromise = VideoModel.loadAndPopulateAccountAndServerAndTags(videoId)
36 } else {
37 return ClientHtml.getIndexHTML(req, res) 35 return ClientHtml.getIndexHTML(req, res)
38 } 36 }
39 37
40 const [ html, video ] = await Promise.all([ 38 const [ html, video ] = await Promise.all([
41 ClientHtml.getIndexHTML(req, res), 39 ClientHtml.getIndexHTML(req, res),
42 videoPromise 40 VideoModel.loadAndPopulateAccountAndServerAndTags(videoId)
43 ]) 41 ])
44 42
45 // Let Angular application handle errors 43 // Let Angular application handle errors
@@ -49,14 +47,44 @@ export class ClientHtml {
49 47
50 let customHtml = ClientHtml.addTitleTag(html, escapeHTML(video.name)) 48 let customHtml = ClientHtml.addTitleTag(html, escapeHTML(video.name))
51 customHtml = ClientHtml.addDescriptionTag(customHtml, escapeHTML(video.description)) 49 customHtml = ClientHtml.addDescriptionTag(customHtml, escapeHTML(video.description))
52 customHtml = ClientHtml.addOpenGraphAndOEmbedTags(customHtml, video) 50 customHtml = ClientHtml.addVideoOpenGraphAndOEmbedTags(customHtml, video)
51
52 return customHtml
53 }
54
55 static async getAccountHTMLPage (nameWithHost: string, req: express.Request, res: express.Response) {
56 return this.getAccountOrChannelHTMLPage(() => AccountModel.loadByNameWithHost(nameWithHost), req, res)
57 }
58
59 static async getVideoChannelHTMLPage (nameWithHost: string, req: express.Request, res: express.Response) {
60 return this.getAccountOrChannelHTMLPage(() => VideoChannelModel.loadByNameWithHostAndPopulateAccount(nameWithHost), req, res)
61 }
62
63 private static async getAccountOrChannelHTMLPage (
64 loader: () => Bluebird<AccountModel | VideoChannelModel>,
65 req: express.Request,
66 res: express.Response
67 ) {
68 const [ html, entity ] = await Promise.all([
69 ClientHtml.getIndexHTML(req, res),
70 loader()
71 ])
72
73 // Let Angular application handle errors
74 if (!entity) {
75 return ClientHtml.getIndexHTML(req, res)
76 }
77
78 let customHtml = ClientHtml.addTitleTag(html, escapeHTML(entity.getDisplayName()))
79 customHtml = ClientHtml.addDescriptionTag(customHtml, escapeHTML(entity.description))
80 customHtml = ClientHtml.addAccountOrChannelMetaTags(customHtml, entity)
53 81
54 return customHtml 82 return customHtml
55 } 83 }
56 84
57 private static async getIndexHTML (req: express.Request, res: express.Response, paramLang?: string) { 85 private static async getIndexHTML (req: express.Request, res: express.Response, paramLang?: string) {
58 const path = ClientHtml.getIndexPath(req, res, paramLang) 86 const path = ClientHtml.getIndexPath(req, res, paramLang)
59 if (ClientHtml.htmlCache[path]) return ClientHtml.htmlCache[path] 87 if (ClientHtml.htmlCache[ path ]) return ClientHtml.htmlCache[ path ]
60 88
61 const buffer = await readFile(path) 89 const buffer = await readFile(path)
62 90
@@ -64,7 +92,7 @@ export class ClientHtml {
64 92
65 html = ClientHtml.addCustomCSS(html) 93 html = ClientHtml.addCustomCSS(html)
66 94
67 ClientHtml.htmlCache[path] = html 95 ClientHtml.htmlCache[ path ] = html
68 96
69 return html 97 return html
70 } 98 }
@@ -114,7 +142,7 @@ export class ClientHtml {
114 return htmlStringPage.replace(CUSTOM_HTML_TAG_COMMENTS.CUSTOM_CSS, styleTag) 142 return htmlStringPage.replace(CUSTOM_HTML_TAG_COMMENTS.CUSTOM_CSS, styleTag)
115 } 143 }
116 144
117 private static addOpenGraphAndOEmbedTags (htmlStringPage: string, video: VideoModel) { 145 private static addVideoOpenGraphAndOEmbedTags (htmlStringPage: string, video: VideoModel) {
118 const previewUrl = CONFIG.WEBSERVER.URL + video.getPreviewStaticPath() 146 const previewUrl = CONFIG.WEBSERVER.URL + video.getPreviewStaticPath()
119 const videoUrl = CONFIG.WEBSERVER.URL + video.getWatchStaticPath() 147 const videoUrl = CONFIG.WEBSERVER.URL + video.getWatchStaticPath()
120 148
@@ -174,7 +202,7 @@ export class ClientHtml {
174 202
175 // Opengraph 203 // Opengraph
176 Object.keys(openGraphMetaTags).forEach(tagName => { 204 Object.keys(openGraphMetaTags).forEach(tagName => {
177 const tagValue = openGraphMetaTags[tagName] 205 const tagValue = openGraphMetaTags[ tagName ]
178 206
179 tagsString += `<meta property="${tagName}" content="${tagValue}" />` 207 tagsString += `<meta property="${tagName}" content="${tagValue}" />`
180 }) 208 })
@@ -190,6 +218,17 @@ export class ClientHtml {
190 // SEO, use origin video url so Google does not index remote videos 218 // SEO, use origin video url so Google does not index remote videos
191 tagsString += `<link rel="canonical" href="${video.url}" />` 219 tagsString += `<link rel="canonical" href="${video.url}" />`
192 220
193 return htmlStringPage.replace(CUSTOM_HTML_TAG_COMMENTS.OPENGRAPH_AND_OEMBED, tagsString) 221 return this.addOpenGraphAndOEmbedTags(htmlStringPage, tagsString)
222 }
223
224 private static addAccountOrChannelMetaTags (htmlStringPage: string, entity: AccountModel | VideoChannelModel) {
225 // SEO, use origin account or channel URL
226 const metaTags = `<link rel="canonical" href="${entity.Actor.url}" />`
227
228 return this.addOpenGraphAndOEmbedTags(htmlStringPage, metaTags)
229 }
230
231 private static addOpenGraphAndOEmbedTags (htmlStringPage: string, metaTags: string) {
232 return htmlStringPage.replace(CUSTOM_HTML_TAG_COMMENTS.META_TAGS, metaTags)
194 } 233 }
195} 234}