Skip to content

Commit e764ae9

Browse files
committed
fix(website): tighten SEO host consistency and technical signals
Prefer apex patternsocd.com in docs/tests, drop duplicate robots meta, enrich BlogPosting JSON-LD, refresh sitemap lastmod, add security headers, and deepen internal links from homepage and guide pages.
1 parent 8533e55 commit e764ae9

14 files changed

Lines changed: 125 additions & 40 deletions

File tree

website/README.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# Patterns Website
22

3-
Marketing site for [Patterns](https://patterns.maskedsyntax.com/), built with SvelteKit and deployed to GitHub Pages.
3+
Marketing site for [Patterns](https://patternsocd.com/), built with SvelteKit.
4+
5+
**Preferred host is the apex domain:** `https://patternsocd.com/` (no `www`).
6+
Canonicals, Open Graph URLs, the sitemap, and `robots.txt` all use apex. DNS
7+
should 308 `www.patternsocd.com``patternsocd.com`, not the other way around.
48

59
## Development
610

@@ -23,4 +27,10 @@ Static output is written to `build/`.
2327

2428
## Deploy
2529

26-
Pushes to `master` that change `website/**` trigger the GitHub Actions workflow at `.github/workflows/deploy-website.yml`.
30+
Production deploys via Vercel. Pushes that change `website/**` should rebuild
31+
the site. After domain changes, confirm:
32+
33+
```bash
34+
curl -sI https://patternsocd.com/ # 200
35+
curl -sI https://www.patternsocd.com/ # 308 → https://patternsocd.com/
36+
```

website/src/lib/data/links.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,13 @@ describe('store links', () => {
3939
expect(new URL(links.macos).searchParams.get('mt')).toBe('12');
4040
});
4141
});
42+
43+
describe('canonical site host', () => {
44+
it('uses the apex domain with https and a trailing slash', () => {
45+
const url = new URL(links.site);
46+
expect(url.protocol).toBe('https:');
47+
expect(url.hostname).toBe('patternsocd.com');
48+
expect(url.hostname.startsWith('www.')).toBe(false);
49+
expect(links.site.endsWith('/')).toBe(true);
50+
});
51+
});

website/src/lib/data/site.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { links } from './links';
3+
import { site } from './site';
4+
5+
describe('site metadata', () => {
6+
it('keeps absolute asset URLs on the apex host', () => {
7+
for (const url of [site.url, site.ogImage, site.logoUrl]) {
8+
expect(url.startsWith(links.site) || url === links.site).toBe(true);
9+
expect(url).not.toMatch(/https?:\/\/www\.patternsocd\.com/i);
10+
}
11+
});
12+
13+
it('exposes logo dimensions for JSON-LD ImageObject', () => {
14+
expect(site.logoWidth).toBeGreaterThan(0);
15+
expect(site.logoHeight).toBeGreaterThan(0);
16+
});
17+
});

website/src/lib/data/site.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,15 @@ export const site = {
99
name: 'Patterns',
1010
/** Keep in sync with the Flutter app's pubspec version (marketing version). */
1111
appVersion: '1.5.0',
12+
/** Preferred host is the apex domain (no www). See website/README.md. */
1213
url: links.site,
1314
/** Purpose-built 1200×630 social share image. */
1415
ogImage: `${links.site}og/patterns-og.png`,
1516
ogImageWidth: 1200,
1617
ogImageHeight: 630,
18+
logoUrl: `${links.site}assets/logo.png`,
19+
logoWidth: 512,
20+
logoHeight: 512,
1721
author: { name: 'MaskedSyntax', url: 'https://github.com/maskedsyntax' },
1822
publisher: { name: 'MaskedSyntax', url: links.maskedsyntax }
1923
} as const;

website/src/lib/sections/BuiltForErp.svelte

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@
5757
<AnimatedOnScroll delay={150}>
5858
<div class="cta-row">
5959
<a href="/erp" class="link-pill">Learn how ERP works in Patterns <ArrowRight size={16} /></a>
60+
<a href="/blog/what-is-erp-exposure-response-prevention" class="link-pill ghost"
61+
>What is ERP? <ArrowRight size={16} /></a
62+
>
6063
</div>
6164
</AnimatedOnScroll>
6265
</ContentContainer>
@@ -144,7 +147,9 @@
144147
145148
.cta-row {
146149
display: flex;
150+
flex-wrap: wrap;
147151
justify-content: center;
152+
gap: 14px;
148153
margin-top: 40px;
149154
}
150155
@@ -158,13 +163,25 @@
158163
font-weight: 600;
159164
color: #000;
160165
background: var(--accent);
161-
transition: opacity 0.2s;
166+
transition: opacity 0.2s, background 0.2s, color 0.2s;
162167
}
163168
164169
.link-pill:hover {
165170
opacity: 0.9;
166171
}
167172
173+
.link-pill.ghost {
174+
color: var(--text);
175+
background: transparent;
176+
border: 1px solid color-mix(in srgb, var(--border) 80%, transparent);
177+
}
178+
179+
.link-pill.ghost:hover {
180+
border-color: color-mix(in srgb, var(--accent) 55%, transparent);
181+
color: var(--accent);
182+
opacity: 1;
183+
}
184+
168185
@media (min-width: 600px) {
169186
.grid {
170187
grid-template-columns: repeat(3, 1fr);

website/src/lib/sections/CbtGuide.svelte

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
intro="Cognitive behavioral therapy (CBT) is a family of practical, evidence-based therapies. For OCD specifically, the CBT approach with the strongest evidence is ERP - exposure and response prevention. Patterns is a CBT and ERP app built around that work."
1111
related={[
1212
{ label: 'How ERP works in Patterns', href: '/erp' },
13-
{ label: 'Understanding the OCD cycle', href: '/ocd' },
14-
{ label: 'Frequently asked questions', href: '/faq' }
13+
{ label: 'ERP vs CBT for OCD', href: '/blog/erp-vs-cbt-for-ocd' },
14+
{ label: 'Is Patterns a CBT app?', href: '/blog/is-patterns-a-cbt-app' },
15+
{ label: 'Understanding the OCD cycle', href: '/ocd' }
1516
]}
1617
>
1718
<p class="lead">

website/src/lib/sections/ErpGuide.svelte

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@
99
title="The urge will pass - even if you don't act on it."
1010
intro="Exposure and Response Prevention (ERP) is the most evidence-based therapy for OCD. It works by teaching your brain, through experience rather than reassurance, that anxiety fades on its own. Patterns is built to support that work between sessions."
1111
related={[
12-
{ label: 'CBT for OCD, and how ERP fits', href: '/cbt' },
13-
{ label: 'Understanding the OCD cycle', href: '/ocd' },
14-
{ label: 'Frequently asked questions', href: '/faq' }
12+
{ label: 'What is ERP? Plain-English guide', href: '/blog/what-is-erp-exposure-response-prevention' },
13+
{ label: 'How to build an exposure hierarchy', href: '/blog/how-to-build-an-exposure-hierarchy' },
14+
{ label: 'The compulsion delay technique', href: '/blog/compulsion-delay-technique' },
15+
{ label: 'Understanding the OCD cycle', href: '/ocd' }
1516
]}
1617
>
1718
<p class="lead">

website/src/lib/sections/OcdGuide.svelte

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
intro="Obsessive-compulsive disorder isn't about being tidy or liking things 'just so.' It's an exhausting loop between thoughts you don't want and the things you do to feel safe. Understanding that loop is the first step to loosening its grip."
1111
related={[
1212
{ label: 'How ERP works in Patterns', href: '/erp' },
13-
{ label: 'CBT for OCD explained', href: '/cbt' },
14-
{ label: 'Frequently asked questions', href: '/faq' }
13+
{ label: 'Pure O and mental compulsions', href: '/blog/pure-o-mental-compulsions' },
14+
{ label: 'OCD vs anxiety', href: '/blog/ocd-vs-anxiety-difference' },
15+
{ label: 'The Patterns blog', href: '/blog' }
1516
]}
1617
>
1718
<p class="lead">

website/src/lib/sections/Understanding.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
<div class="cta-row">
4848
<a href="/ocd" class="link-pill">Understand the OCD cycle <ArrowRight size={16} /></a>
4949
<a href="/erp" class="link-pill ghost">How ERP helps <ArrowRight size={16} /></a>
50+
<a href="/blog" class="link-pill ghost">From the blog <ArrowRight size={16} /></a>
5051
</div>
5152
</AnimatedOnScroll>
5253
</ContentContainer>

website/src/routes/+layout.svelte

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,6 @@
1616
const bareRoutes = ['/get'];
1717
const bare = $derived(bareRoutes.includes($page.url.pathname.replace(/\/$/, '') || '/'));
1818
19-
// Internal tooling under /dev sets its own noindex tag. Emitting the global
20-
// index directive here too would leave the page with two conflicting robots
21-
// metas, and crawlers may honour either one.
22-
const internal = $derived($page.url.pathname.startsWith('/dev'));
23-
2419
// Campaign params are captured on every entry, not just /get, and the first
2520
// valid one wins for the session — so a visitor who lands on an ad URL and
2621
// then browses to /erp still has their store click credited to the ad.
@@ -47,20 +42,14 @@
4742
</script>
4843

4944
<svelte:head>
50-
<!-- Global, always-on tags. Per-page title/description/canonical/OG/JSON-LD
51-
are set by the <Seo> component in each route. -->
45+
<!-- Global chrome only. Title/description/canonical/OG/robots/JSON-LD come
46+
from <Seo> on each public route. /dev pages set their own noindex. -->
5247
<meta charset="utf-8" />
5348
<meta name="viewport" content="width=device-width, initial-scale=1" />
5449
<meta name="author" content="MaskedSyntax" />
55-
{#if !internal}
56-
<meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1" />
57-
<meta name="googlebot" content="index, follow, max-image-preview:large, max-snippet:-1" />
58-
{/if}
5950
<meta name="application-name" content="Patterns" />
6051
<meta name="theme-color" content="#0A0A0A" />
6152
<meta name="color-scheme" content="dark light" />
62-
<meta property="og:site_name" content="Patterns" />
63-
<meta property="og:locale" content="en_US" />
6453
<meta name="mobile-web-app-capable" content="yes" />
6554
<meta name="apple-mobile-web-app-capable" content="yes" />
6655
<meta name="apple-mobile-web-app-status-bar-style" content="black" />

0 commit comments

Comments
 (0)