@vakwerk/seo 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/url.d.ts +4 -0
- package/dist/url.d.ts.map +1 -0
- package/dist/url.js +28 -0
- package/dist/url.js.map +1 -0
- package/package.json +26 -0
- package/src/SEO.astro +49 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface SEOProps {
|
|
2
|
+
title: string;
|
|
3
|
+
titleTemplate?: string;
|
|
4
|
+
description: string;
|
|
5
|
+
siteName: string;
|
|
6
|
+
canonicalUrl?: string | URL;
|
|
7
|
+
type?: 'website' | 'article';
|
|
8
|
+
image?: {
|
|
9
|
+
src: string;
|
|
10
|
+
alt?: string;
|
|
11
|
+
};
|
|
12
|
+
locale?: string;
|
|
13
|
+
noindex?: boolean;
|
|
14
|
+
schemas?: object[];
|
|
15
|
+
twitterCard?: 'summary' | 'summary_large_image';
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC;IAC5B,IAAI,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAC7B,KAAK,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,WAAW,CAAC,EAAE,SAAS,GAAG,qBAAqB,CAAC;CACjD"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/dist/url.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function resolveCanonicalUrl(pathname: string, site: URL | undefined, override?: string | URL): string;
|
|
2
|
+
export declare function resolveTitle(title: string, template?: string): string;
|
|
3
|
+
export declare function resolveImageUrl(src: string, site: URL | undefined): string;
|
|
4
|
+
//# sourceMappingURL=url.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url.d.ts","sourceRoot":"","sources":["../src/url.ts"],"names":[],"mappings":"AAAA,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,GAAG,GAAG,SAAS,EACrB,QAAQ,CAAC,EAAE,MAAM,GAAG,GAAG,GACtB,MAAM,CAWR;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAMrE;AAED,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,GAAG,GAAG,SAAS,GACpB,MAAM,CAUR"}
|
package/dist/url.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function resolveCanonicalUrl(pathname, site, override) {
|
|
2
|
+
if (override !== undefined) {
|
|
3
|
+
return override instanceof URL ? override.href : override;
|
|
4
|
+
}
|
|
5
|
+
if (!site) {
|
|
6
|
+
throw new Error('@vakwerk/seo: cannot resolve canonical URL without Astro.site or an override');
|
|
7
|
+
}
|
|
8
|
+
const normalized = pathname === '/' ? '/' : pathname.replace(/\/+$/, '');
|
|
9
|
+
return new URL(normalized, site).href;
|
|
10
|
+
}
|
|
11
|
+
export function resolveTitle(title, template) {
|
|
12
|
+
if (!template)
|
|
13
|
+
return title;
|
|
14
|
+
if (!template.includes('%s')) {
|
|
15
|
+
throw new Error('@vakwerk/seo: titleTemplate must contain a %s placeholder');
|
|
16
|
+
}
|
|
17
|
+
return template.replace('%s', title);
|
|
18
|
+
}
|
|
19
|
+
export function resolveImageUrl(src, site) {
|
|
20
|
+
if (src.startsWith('http://') || src.startsWith('https://')) {
|
|
21
|
+
return src;
|
|
22
|
+
}
|
|
23
|
+
if (!site) {
|
|
24
|
+
throw new Error('@vakwerk/seo: cannot resolve relative image URL without Astro.site');
|
|
25
|
+
}
|
|
26
|
+
return new URL(src, site).href;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=url.js.map
|
package/dist/url.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url.js","sourceRoot":"","sources":["../src/url.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,mBAAmB,CACjC,QAAgB,EAChB,IAAqB,EACrB,QAAuB;IAEvB,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,OAAO,QAAQ,YAAY,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC;IAC5D,CAAC;IAED,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC,8EAA8E,CAAC,CAAC;IAClG,CAAC;IAED,MAAM,UAAU,GAAG,QAAQ,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACzE,OAAO,IAAI,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAa,EAAE,QAAiB;IAC3D,IAAI,CAAC,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5B,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,2DAA2D,CAAC,CAAC;IAC/E,CAAC;IACD,OAAO,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,GAAW,EACX,IAAqB;IAErB,IAAI,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5D,OAAO,GAAG,CAAC;IACb,CAAC;IAED,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC,oEAAoE,CAAC,CAAC;IACxF,CAAC;IAED,OAAO,IAAI,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC;AACjC,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vakwerk/seo",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"import": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"./SEO.astro": "./src/SEO.astro"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"src/SEO.astro"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"test:watch": "vitest",
|
|
20
|
+
"check:types": "tsc --noEmit",
|
|
21
|
+
"lint": "echo 'no linter configured yet'"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"astro": ">=5.0.0"
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/SEO.astro
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { SEOProps } from './types.js';
|
|
3
|
+
import { resolveCanonicalUrl, resolveImageUrl, resolveTitle } from './url.js';
|
|
4
|
+
|
|
5
|
+
type Props = SEOProps;
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
title: rawTitle,
|
|
9
|
+
titleTemplate,
|
|
10
|
+
description,
|
|
11
|
+
siteName,
|
|
12
|
+
canonicalUrl,
|
|
13
|
+
type = 'website',
|
|
14
|
+
image,
|
|
15
|
+
locale = 'nl_NL',
|
|
16
|
+
noindex = false,
|
|
17
|
+
schemas = [],
|
|
18
|
+
twitterCard,
|
|
19
|
+
} = Astro.props;
|
|
20
|
+
|
|
21
|
+
const title = resolveTitle(rawTitle, titleTemplate);
|
|
22
|
+
const canonical = resolveCanonicalUrl(Astro.url.pathname, Astro.site, canonicalUrl);
|
|
23
|
+
const resolvedImage = image ? resolveImageUrl(image.src, Astro.site) : undefined;
|
|
24
|
+
const card = twitterCard ?? (image ? 'summary_large_image' : 'summary');
|
|
25
|
+
const robots = noindex ? 'noindex, nofollow' : 'index, follow';
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
<title>{title}</title>
|
|
29
|
+
<meta name="description" content={description} />
|
|
30
|
+
<link rel="canonical" href={canonical} />
|
|
31
|
+
<meta name="robots" content={robots} />
|
|
32
|
+
|
|
33
|
+
<meta property="og:type" content={type} />
|
|
34
|
+
<meta property="og:title" content={title} />
|
|
35
|
+
<meta property="og:description" content={description} />
|
|
36
|
+
<meta property="og:url" content={canonical} />
|
|
37
|
+
<meta property="og:locale" content={locale} />
|
|
38
|
+
<meta property="og:site_name" content={siteName} />
|
|
39
|
+
{resolvedImage && <meta property="og:image" content={resolvedImage} />}
|
|
40
|
+
{resolvedImage && image?.alt && <meta property="og:image:alt" content={image.alt} />}
|
|
41
|
+
|
|
42
|
+
<meta name="twitter:card" content={card} />
|
|
43
|
+
<meta name="twitter:title" content={title} />
|
|
44
|
+
<meta name="twitter:description" content={description} />
|
|
45
|
+
{resolvedImage && <meta name="twitter:image" content={resolvedImage} />}
|
|
46
|
+
|
|
47
|
+
{schemas.map((schema) => (
|
|
48
|
+
<script is:inline type="application/ld+json" set:html={JSON.stringify(schema).replace(/<\/script>/gi, '<\\/script>')} />
|
|
49
|
+
))}
|