diff options
author | Chocobozzz <me@florianbigard.com> | 2019-02-21 14:06:10 +0100 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2019-02-21 14:06:10 +0100 |
commit | 92bf2f62995bbaa0402cb4657473ad8d5b6fcf8d (patch) | |
tree | 7f3f34b1503fd21db7f0b913c3b908f004015011 /server/lib/client-html.ts | |
parent | 84c7cde6e81426a42e7aa29187b473bc89f1c8f6 (diff) | |
download | PeerTube-92bf2f62995bbaa0402cb4657473ad8d5b6fcf8d.tar.gz PeerTube-92bf2f62995bbaa0402cb4657473ad8d5b6fcf8d.tar.zst PeerTube-92bf2f62995bbaa0402cb4657473ad8d5b6fcf8d.zip |
Improve channel and account SEO
Diffstat (limited to 'server/lib/client-html.ts')
-rw-r--r-- | server/lib/client-html.ts | 67 |
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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import * as Bluebird from 'bluebird' | ||
3 | import { buildFileLocale, getDefaultLocale, is18nLocale, POSSIBLE_LOCALES } from '../../shared/models/i18n/i18n' | 2 | import { buildFileLocale, getDefaultLocale, is18nLocale, POSSIBLE_LOCALES } from '../../shared/models/i18n/i18n' |
4 | import { CONFIG, CUSTOM_HTML_TAG_COMMENTS, EMBED_SIZE } from '../initializers' | 3 | import { CONFIG, CUSTOM_HTML_TAG_COMMENTS, EMBED_SIZE } from '../initializers' |
5 | import { join } from 'path' | 4 | import { join } from 'path' |
@@ -9,10 +8,13 @@ import * as validator from 'validator' | |||
9 | import { VideoPrivacy } from '../../shared/models/videos' | 8 | import { VideoPrivacy } from '../../shared/models/videos' |
10 | import { readFile } from 'fs-extra' | 9 | import { readFile } from 'fs-extra' |
11 | import { getActivityStreamDuration } from '../models/video/video-format-utils' | 10 | import { getActivityStreamDuration } from '../models/video/video-format-utils' |
11 | import { AccountModel } from '../models/account/account' | ||
12 | import { VideoChannelModel } from '../models/video/video-channel' | ||
13 | import * as Bluebird from 'bluebird' | ||
12 | 14 | ||
13 | export class ClientHtml { | 15 | export 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 | } |