Skip to content

Commit 7d2ae43

Browse files
committed
fix: disabling bot crawling on staging and refactor sitemap to add dates and images
1 parent 3584f66 commit 7d2ae43

File tree

10 files changed

+128
-66
lines changed

10 files changed

+128
-66
lines changed

public/robots.txt

Lines changed: 0 additions & 3 deletions
This file was deleted.

src/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { DeviceType, ImageExtensionType, ImageFormatType } from '@/types';
44
export const IS_SSR = import.meta.env?.SSR ?? false;
55
export const IS_PRERENDER = import.meta.env?.MODE === 'prerender';
66
export const HOST_URL = getEnv<string>('VITE_HOST_URL') || 'https://blog.eleven-labs.com';
7+
export const IS_ENV_PRODUCTION = HOST_URL === 'https://blog.eleven-labs.com';
78
export const BASE_URL = import.meta.env?.BASE_URL || '/';
89

910
export const IS_DEBUG = getEnv<string>('VITE_IS_DEBUG') === 'true';

src/helpers/assetHelper.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export const getCoverPath = ({
4444
extension?: ImageExtensionType;
4545
position?: ImagePositionType;
4646
}): string => {
47-
const isProd: boolean = process.env.NODE_ENV === 'production';
47+
const isProd = process.env.NODE_ENV === 'production';
4848
const directoryPath = dirname(path);
4949
const filename = basename(path, extname(path));
5050
const imageFormat = SIZES_BY_IMAGE_FORMAT[device][format];
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { writeFileSync } from 'node:fs';
2+
import { resolve } from 'node:path';
3+
4+
import { HOST_URL, IS_ENV_PRODUCTION } from '@/constants';
5+
6+
export const getRobotsTxt = (): string => {
7+
return (
8+
IS_ENV_PRODUCTION
9+
? ['User-agent: *', 'Allow: /', `Sitemap: ${HOST_URL}/sitemap.xml`]
10+
: ['User-agent: *', 'Disallow: /']
11+
).join('\n');
12+
};
13+
14+
export const generateRobotsTxt = async (options: { rootDir: string }): Promise<void> => {
15+
const robotsTxt = getRobotsTxt();
16+
writeFileSync(resolve(options.rootDir, 'robots.txt'), robotsTxt, 'utf8');
17+
};

src/helpers/prerenderHelper/generateSitemap.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,32 @@ import * as xml2js from 'xml2js';
44

55
import { DEFAULT_LANGUAGE } from '@/constants';
66
import { generateUrl } from '@/helpers/assetHelper';
7+
import type { SitemapEntry } from '@/helpers/prerenderHelper/getSitemapEntries';
78

8-
export const getSitemap = (
9-
sitemapEntries: { links: { lang: string; url: string }[]; changefreq?: string; priority?: number }[]
10-
): string => {
9+
export const getSitemap = (sitemapEntries: SitemapEntry[]): string => {
1110
const builder = new xml2js.Builder();
1211
return builder.buildObject({
1312
urlset: {
1413
$: {
1514
xmlns: 'http://www.sitemaps.org/schemas/sitemap/0.9',
1615
'xmlns:xhtml': 'http://www.w3.org/1999/xhtml',
17-
'xmlns:news': 'http://www.google.com/schemas/sitemap-news/0.9',
16+
'xmlns:image': 'http://www.google.com/schemas/sitemap-image/1.1',
1817
},
19-
url: sitemapEntries.map(({ links, priority, changefreq }) => {
18+
url: sitemapEntries.map(({ links, priority, changeFrequency, lastModified, image }) => {
2019
const defaultLink = links.find((link) => link.lang === DEFAULT_LANGUAGE) ?? links[0];
2120
return {
2221
loc: generateUrl(defaultLink.url),
23-
...(changefreq ? { changefreq } : {}),
24-
priority: priority?.toFixed(1) ?? 0.3,
22+
...(lastModified ? { lastmod: lastModified } : {}),
23+
...(changeFrequency ? { changefreq: changeFrequency } : {}),
24+
...(priority ? { priority } : {}),
25+
...(image
26+
? {
27+
'image:image': {
28+
'image:loc': `${blogUrl}${image.url}`,
29+
...(image.description ? { 'image:caption': image.description } : {}),
30+
},
31+
}
32+
: {}),
2533
...(links.length > 1
2634
? {
2735
'xhtml:link': links.map((link) => ({

src/helpers/prerenderHelper/getSitemapEntries.test.ts

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getSitemapEntries } from './getSitemapEntries';
1+
import { getSitemapEntries, SitemapEntry } from './getSitemapEntries';
22

33
describe('getSitemapEntries', () => {
44
test('should generate sitemap entries correctly', () => {
@@ -11,38 +11,53 @@ describe('getSitemapEntries', () => {
1111
};
1212
});
1313
vi.mock('@/helpers/markdownContentManagerHelper', () => ({
14-
getPosts: (): { lang: string; slug: string; categories: string[]; authors: string[] }[] => [
15-
{ lang: 'fr', slug: 'post-1', categories: ['architecture'], authors: ['author-1'] },
16-
{ lang: 'en', slug: 'post-2', categories: ['php'], authors: ['author-1'] },
14+
getPosts: (): {
15+
lang: string;
16+
slug: string;
17+
categories: string[];
18+
authors: string[];
19+
date: string;
20+
cover?: { path: string };
21+
}[] => [
22+
{
23+
lang: 'fr',
24+
slug: 'post-1',
25+
categories: ['architecture'],
26+
authors: ['author-1'],
27+
date: '2024-01-01T00:00:00',
28+
cover: { path: '/imgs/post-1/cover.png' },
29+
},
30+
{ lang: 'en', slug: 'post-2', categories: ['php'], authors: ['author-1'], date: '2024-01-01T00:00:00' },
1731
],
1832
getAuthors: (): { username: string }[] => [{ username: 'author-1' }],
1933
}));
2034

2135
// Expected result
22-
const expectedSitemapEntries = [
23-
{ priority: 1, links: [{ lang: 'fr', url: '/fr/post-1/' }] },
24-
{ priority: 1, links: [{ lang: 'en', url: '/en/post-2/' }] },
36+
const expectedSitemapEntries: SitemapEntry[] = [
2537
{
26-
priority: 0.8,
27-
changefreq: 'weekly',
38+
links: [{ lang: 'fr', url: '/fr/post-1/' }],
39+
lastModified: '2024-01-01T00:00:00',
40+
image: { url: '/imgs/post-1/cover-w400-h245-x2.avif' },
41+
},
42+
{ links: [{ lang: 'en', url: '/en/post-2/' }], lastModified: '2024-01-01T00:00:00' },
43+
{
44+
changeFrequency: 'weekly',
2845
links: [
2946
{ lang: 'fr', url: '/' },
3047
{ lang: 'fr', url: '/fr/' },
3148
{ lang: 'en', url: '/en/' },
3249
],
3350
},
3451
{
35-
priority: 0.7,
36-
changefreq: 'weekly',
52+
changeFrequency: 'weekly',
3753
links: [
3854
{ lang: 'fr', url: '/fr/categories/all/' },
3955
{ lang: 'en', url: '/en/categories/all/' },
4056
],
4157
},
42-
{ priority: 0.7, changefreq: 'weekly', links: [{ lang: 'en', url: '/en/categories/php/' }] },
43-
{ priority: 0.7, changefreq: 'weekly', links: [{ lang: 'fr', url: '/fr/categories/architecture/' }] },
58+
{ changeFrequency: 'weekly', links: [{ lang: 'en', url: '/en/categories/php/' }] },
59+
{ changeFrequency: 'weekly', links: [{ lang: 'fr', url: '/fr/categories/architecture/' }] },
4460
{
45-
priority: 0.5,
4661
links: [
4762
{ lang: 'fr', url: '/fr/authors/author-1/' },
4863
{ lang: 'en', url: '/en/authors/author-1/' },
Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,74 @@
1+
import { DEVICES, IMAGE_FORMATS, PATHS } from '@/constants';
2+
import { getCoverPath } from '@/helpers/assetHelper';
13
import { getAuthors, getPosts } from '@/helpers/markdownContentManagerHelper';
24
import {
35
getAuthorPageUrls,
46
getCategoryPageUrls,
57
getHomePageUrls,
6-
getPostPageUrls,
78
getTutorialStepPageUrls,
89
} from '@/helpers/prerenderHelper/getUrls';
10+
import { generatePath } from '@/helpers/routerHelper';
911

10-
type Link = {
11-
lang: string;
12-
url: string;
13-
};
14-
15-
type SitemapEntry = {
16-
links: Link[];
17-
changefreq?: string;
18-
priority: number;
19-
};
12+
export interface SitemapEntry {
13+
links: {
14+
lang: string;
15+
url: string;
16+
}[];
17+
image?: {
18+
url: string;
19+
description?: string;
20+
};
21+
lastModified?: string;
22+
changeFrequency?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';
23+
priority?: number;
24+
}
2025

2126
export const getSitemapEntries = (): SitemapEntry[] => {
2227
const posts = getPosts();
2328
const authors = getAuthors();
2429

2530
const rootEntry: SitemapEntry = {
26-
priority: 0.8,
2731
links: getHomePageUrls(),
28-
changefreq: 'weekly',
32+
changeFrequency: 'weekly',
2933
};
3034

3135
const categoryPageUrls = getCategoryPageUrls(posts);
3236
const categoryEntries: SitemapEntry[] = categoryPageUrls.map((urls) => ({
33-
priority: 0.7,
3437
links: urls,
35-
changefreq: 'weekly',
38+
changeFrequency: 'weekly',
3639
}));
3740

3841
const authorPageUrls = getAuthorPageUrls(posts, authors);
3942
const authorEntries: SitemapEntry[] = authorPageUrls.map((urls) => ({
40-
priority: 0.5,
4143
links: urls,
4244
}));
4345

44-
const postPageUrls = getPostPageUrls(posts);
45-
const postEntries: SitemapEntry[] = postPageUrls.map((urls) => ({
46-
priority: 1,
47-
links: urls,
46+
const postEntries: SitemapEntry[] = posts.map((post) => ({
47+
links: [
48+
{
49+
lang: post.lang,
50+
url: generatePath(PATHS.POST, { lang: post.lang, slug: post.slug }),
51+
},
52+
],
53+
lastModified: post.date,
54+
image: post.cover?.path
55+
? {
56+
url: getCoverPath({
57+
path: post.cover?.path,
58+
format: IMAGE_FORMATS.HIGHLIGHTED_TUTORIAL_POST_CARD_COVER,
59+
pixelRatio: 2,
60+
device: DEVICES.DESKTOP,
61+
position: post.cover.position,
62+
}),
63+
description: post.cover?.alt,
64+
}
65+
: undefined,
4866
}));
4967

5068
const tutorialStepUrls = getTutorialStepPageUrls(posts);
5169
const tutorialStepEntries: SitemapEntry[] = tutorialStepUrls.map((urls) => ({
52-
priority: 0.9,
5370
links: urls,
5471
}));
5572

56-
return [rootEntry, ...categoryEntries, ...authorEntries, ...postEntries, ...tutorialStepEntries].sort(
57-
(a, b) => b?.priority - a?.priority
58-
);
73+
return [...postEntries, rootEntry, ...categoryEntries, ...authorEntries, ...tutorialStepEntries];
5974
};

src/helpers/prerenderHelper/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { resolve } from 'node:path';
22

33
import { DEFAULT_LANGUAGE, LANGUAGES_AVAILABLE, PATHS } from '@/constants';
4+
import { generateRobotsTxt } from '@/helpers/prerenderHelper/generateRobotsTxt';
45
import { generatePath } from '@/helpers/routerHelper';
56

67
import { generateFeedFile } from './generateFeedFile';
@@ -41,6 +42,9 @@ export const generateFiles = async (options: { rootDir: string; baseUrl: string
4142
rootDir: __dirname,
4243
sitemapEntries,
4344
}),
45+
generateRobotsTxt({
46+
rootDir: __dirname,
47+
}),
4448
]);
4549
generateFeedFile({ rootDir: __dirname });
4650

src/middlewares/imageMiddleware.ts renamed to src/requestHandlers/imageHandler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Request, Response } from 'express';
1+
import type { RequestHandler } from 'express';
22
import mime from 'mime';
33
import { existsSync, readFileSync } from 'node:fs';
44
import { resolve } from 'node:path';
@@ -7,7 +7,7 @@ import Sharp from 'sharp';
77
import { DEFAULT_EXTENSION_FOR_IMAGES, IMAGE_CONTENT_TYPES } from '@/constants';
88
import { ImageExtensionType, ImagePositionType } from '@/types';
99

10-
export const imageMiddleware = async (req: Request, res: Response): Promise<unknown> => {
10+
export const imageHandler: RequestHandler = async (req, res) => {
1111
try {
1212
const imagePath = resolve(process.cwd(), 'public', req.path.slice(1) as string);
1313
if (!existsSync(imagePath)) {

src/server.ts

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import chokidar from 'chokidar';
2-
import express from 'express';
2+
import express, { RequestHandler } from 'express';
33
import i18next from 'i18next';
44
import i18nextHttpMiddleware from 'i18next-http-middleware';
55
import { cpSync, statSync } from 'node:fs';
@@ -12,13 +12,25 @@ import { i18nResources } from '@/config/i18n/i18nResources';
1212
import { BASE_URL } from '@/constants';
1313
import { writeJsonDataFiles } from '@/helpers/contentHelper';
1414
import { loadDataByMarkdownFilePath } from '@/helpers/markdownContentManagerHelper';
15+
import { getRobotsTxt } from '@/helpers/prerenderHelper/generateRobotsTxt';
1516
import { getSitemap } from '@/helpers/prerenderHelper/generateSitemap';
1617
import { getSitemapEntries } from '@/helpers/prerenderHelper/getSitemapEntries';
1718
import { createRequestByExpressRequest } from '@/helpers/requestHelper';
18-
import { imageMiddleware } from '@/middlewares/imageMiddleware';
19+
import { imageHandler } from '@/requestHandlers/imageHandler';
1920

2021
const isProd: boolean = process.env.NODE_ENV === 'production';
2122

23+
const robotsTxtHandler: RequestHandler = (_, res) => {
24+
const robotsTxt = getRobotsTxt();
25+
res.status(200).set({ 'Content-Type': 'text/plain' }).end(robotsTxt);
26+
};
27+
28+
const sitemapHandler: RequestHandler = (_, res) => {
29+
const sitemapEntries = getSitemapEntries();
30+
const sitemap = getSitemap(sitemapEntries);
31+
res.status(200).set({ 'Content-Type': 'text/xml' }).end(sitemap);
32+
};
33+
2234
const createServer = async (): Promise<void> => {
2335
i18next.use(i18nextHttpMiddleware.LanguageDetector).init({
2436
...i18nConfig,
@@ -41,13 +53,10 @@ const createServer = async (): Promise<void> => {
4153
dirname: __dirname,
4254
});
4355

56+
app.get(/\/imgs\//, imageHandler);
4457
app.use(BASE_URL, serveStatic(__dirname, { index: false }));
45-
46-
app.get('/sitemap.xml', (_, res) => {
47-
const sitemapEntries = getSitemapEntries();
48-
const sitemap = getSitemap(sitemapEntries);
49-
res.status(200).set({ 'Content-Type': 'text/xml' }).end(sitemap);
50-
});
58+
app.get('/robots.txt', robotsTxtHandler);
59+
app.get('/sitemap.xml', sitemapHandler);
5160

5261
app.use('*', async (req, res, next) => {
5362
try {
@@ -93,14 +102,10 @@ const createServer = async (): Promise<void> => {
93102
});
94103
});
95104

96-
app.get(/\/imgs\//, imageMiddleware);
105+
app.get(/\/imgs\//, imageHandler);
97106
app.use(vite.middlewares);
98-
99-
app.get('/sitemap.xml', (_, res) => {
100-
const sitemapEntries = getSitemapEntries();
101-
const sitemap = getSitemap(sitemapEntries);
102-
res.status(200).set({ 'Content-Type': 'text/xml' }).end(sitemap);
103-
});
107+
app.get('/robots.txt', robotsTxtHandler);
108+
app.get('/sitemap.xml', sitemapHandler);
104109

105110
app.use('*', async (req, res, next) => {
106111
const url = req.originalUrl;

0 commit comments

Comments
 (0)