Skip to content

Commit 45e8a69

Browse files
authored
Merge pull request #2706 from appwrite/fix-SER-1068-search-console-issues
fix: Improve SEO canonicalization and block non-indexable pages
2 parents 57de9c6 + 4caf510 commit 45e8a69

File tree

5 files changed

+101
-16
lines changed

5 files changed

+101
-16
lines changed

server/sitemap.js

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,31 @@ export async function sitemaps() {
2727
console.info('Preparing Sitemap...');
2828
const { manifest } = await import('../build/server/manifest.js');
2929
const threads = collectThreads().map((id) => `/threads/${id}`);
30+
31+
// Internal/auth paths to exclude
32+
const INTERNAL_PATHS = ['/console/login', '/console/register', '/v1/'];
33+
3034
const otherRoutes = manifest._.routes
3135
.filter((r) => r.params.length === 0)
3236
.map((r) => r.id)
33-
.filter(
34-
(id) => !id.startsWith('/threads/') && !id.endsWith('.json') && !id.endsWith('.xml')
35-
);
37+
.filter((id) => {
38+
// Exclude threads (handled separately), JSON/XML endpoints
39+
if (id.startsWith('/threads/') || id.endsWith('.json') || id.endsWith('.xml')) {
40+
return false;
41+
}
42+
43+
// Exclude any docs references that are not the canonical \"cloud\" version
44+
if (id.startsWith('/docs/references/') && !id.startsWith('/docs/references/cloud/')) {
45+
return false;
46+
}
47+
48+
// Exclude internal/auth paths
49+
if (INTERNAL_PATHS.some((path) => id.startsWith(path))) {
50+
return false;
51+
}
52+
53+
return true;
54+
});
3655

3756
mkdirSync(SITEMAP_DIR, { recursive: true });
3857
mkdirSync(THREADS_DIR, { recursive: true });

src/hooks.server.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,12 +222,43 @@ const initSession: Handle = async ({ event, resolve }) => {
222222
return resolve(event);
223223
};
224224

225+
/**
226+
* SEO optimization: noindex internal/auth pages and staging subdomains
227+
*/
228+
const NOINDEX_PATHS = [
229+
/^\/console\/login\/?$/,
230+
/^\/console\/register\/?$/,
231+
/^\/v1\/storage\//,
232+
/^\/v1\//
233+
];
234+
235+
// Block any staging/preview subdomains (e.g., *.cloud.appwrite.io, stage.*, etc.)
236+
const NOINDEX_HOSTS = [/\.cloud\.appwrite\.io$/i, /^stage\./i, /^fra\./i];
237+
238+
const seoOptimization: Handle = async ({ event, resolve }) => {
239+
const { url } = event;
240+
241+
// Check if this is a path or host that should not be indexed
242+
const shouldNoindex =
243+
NOINDEX_PATHS.some((re) => re.test(url.pathname)) ||
244+
NOINDEX_HOSTS.some((re) => re.test(url.hostname));
245+
246+
const response = await resolve(event);
247+
248+
if (shouldNoindex) {
249+
response.headers.set('x-robots-tag', 'noindex, nofollow');
250+
}
251+
252+
return response;
253+
};
254+
225255
export const handle = sequence(
226256
Sentry.sentryHandle(),
227257
markdownHandler,
228258
redirecter,
229259
wwwRedirecter,
230260
securityheaders,
231-
initSession
261+
initSession,
262+
seoOptimization
232263
);
233264
export const handleError = Sentry.handleErrorWithSentry();

src/lib/utils/canonical.ts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,20 @@
11
import { DEFAULT_HOST } from './metadata';
22

3-
/**
4-
* Canonical version for API references - should always point to this version
5-
*/
6-
const CANONICAL_VERSION = 'cloud';
7-
83
/**
94
* Normalizes the version in a docs reference path to the canonical version
105
*/
116
function normalizeDocsVersion(pathname: string): string {
12-
// Match patterns like /docs/references/1.7.x/... or /docs/references/0.15.x/... or /docs/references/1.8.x/...
7+
// Match patterns like /docs/references/1.7.x/... or /docs/references/0.15.x/... or /docs/references/cloud/...
138
const versionPattern = /^\/docs\/references\/([^/]+)\//;
149
const match = pathname.match(versionPattern);
1510

1611
if (match) {
1712
const currentVersion = match[1];
18-
// Always point to cloud version for canonical URLs
19-
// 'cloud' maps to 1.8.x server-side, and is the preferred canonical version
20-
if (currentVersion !== CANONICAL_VERSION) {
21-
return pathname.replace(versionPattern, `/docs/references/${CANONICAL_VERSION}/`);
13+
// Use "cloud" as the single canonical docs version so we don't have to
14+
// maintain a list of explicit versions. Anything that isn't "cloud"
15+
// will point canonically to the /cloud/ variant.
16+
if (currentVersion !== 'cloud') {
17+
return pathname.replace(versionPattern, `/docs/references/cloud/`);
2218
}
2319
}
2420

@@ -28,7 +24,7 @@ function normalizeDocsVersion(pathname: string): string {
2824
/**
2925
* Builds a canonical URL from a page URL, applying smart normalization:
3026
* - Fixes prerender origin issue (uses DEFAULT_HOST during prerendering)
31-
* - For docs references: always points to cloud version
27+
* - For docs references: always points to the chosen canonical version
3228
* - Query strings are already stripped by using pathname (original behavior preserved)
3329
*/
3430
export function getCanonicalUrl(url: URL): string {
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<script lang="ts">
2+
import { page } from '$app/state';
3+
import type { Snippet } from 'svelte';
4+
5+
const { children }: { children: Snippet } = $props();
6+
7+
// Check if the current version is not the canonical "cloud" version
8+
const isNotCloud = $derived(page.params?.version && page.params.version !== 'cloud');
9+
</script>
10+
11+
<svelte:head>
12+
{#if isNotCloud}
13+
<!-- Noindex old documentation versions to avoid duplicates -->
14+
<meta name="robots" content="noindex, follow" />
15+
{/if}
16+
</svelte:head>
17+
18+
{@render children()}

src/routes/robots.txt/+server.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,28 @@
11
import type { RequestHandler } from '@sveltejs/kit';
22

33
const follow = `# robotstxt.org/
4-
User-agent: *`;
4+
User-agent: *
5+
6+
# Block tracking parameters to avoid duplicate indexing
7+
Disallow: /*?utm_
8+
Disallow: /*&utm_
9+
Disallow: /*?ref=
10+
Disallow: /*&ref=
11+
Disallow: /*?trk=
12+
Disallow: /*&trk=
13+
Disallow: /*?adobe_mc=
14+
Disallow: /*&adobe_mc=
15+
16+
# Block internal/auth pages
17+
Disallow: /console/login
18+
Disallow: /console/register
19+
Disallow: /v1/
20+
21+
# Block all versioned docs references
22+
Disallow: /docs/references/*/
23+
24+
# Allow canonical cloud docs
25+
Allow: /docs/references/cloud/`;
526

627
const nofollow = `# robotstxt.org/
728
User-agent: *

0 commit comments

Comments
 (0)