Skip to content

feat(payload-helper): Add SEO and footer components#406

Merged
ainsleyclark merged 2 commits intomainfrom
claude/add-payload-seo-components-odyVR
Feb 6, 2026
Merged

feat(payload-helper): Add SEO and footer components#406
ainsleyclark merged 2 commits intomainfrom
claude/add-payload-seo-components-odyVR

Conversation

@ainsleyclark
Copy link
Contributor

Summary

This PR adds comprehensive SEO support and footer code injection capabilities to the sveltekit-helper package through new Svelte components and utilities for Payload CMS integration.

Key Changes

  • New Components:

    • PayloadSEO.svelte: Renders complete SEO metadata including Open Graph, Twitter Card, canonical URLs, and structured data (JSON-LD)
    • PayloadFooter.svelte: Handles footer code injection from both site-level settings and page-level overrides
  • New Utilities:

    • serializeSchema(): Converts structured data objects to JSON-LD script tags
    • resolveItems(): Helper to resolve Payload relationship fields to their populated objects
  • Type Definitions:

    • PayloadSEOProps: Props for the SEO component
    • PayloadFooterProps: Props for the footer component
    • PayloadMeta: SEO metadata structure
    • PayloadSettings: Site-level settings compatible with Payload CMS
    • PayloadSocial: Social media links configuration
    • PayloadCodeInjection: Header/footer script injection structure
  • Package Configuration:

    • Added ./utils/seo export to package.json for public API access
    • Updated main exports to include new components and utilities

Implementation Details

  • The PayloadSEO component intelligently merges page-level and site-level metadata with proper fallback chains
  • Automatically generates Organization and WebSite structured data schemas based on available settings
  • Supports Open Graph image metadata with dimensions and MIME type
  • Twitter Card support with automatic card type selection based on image availability
  • Comprehensive test coverage for the serializeSchema utility function
  • Uses Svelte 5 runes ($props, $derived) for reactive state management

https://claude.ai/code/session_01ShF8qREiHw7gTnmaxSTk99

Adds SEO head meta rendering (Open Graph, Twitter Card, canonical URL,
structured data, code injection) and footer code injection components
for Payload CMS integration. Extracts serializeSchema as a generic
utility under utils/seo.

https://claude.ai/code/session_01ShF8qREiHw7gTnmaxSTk99
@ainsleyclark ainsleyclark changed the title Add SEO and footer components for Payload CMS integration feat(payload-helper): Add SEO and footer components Feb 5, 2026
@claude

This comment has been minimized.

@codecov
Copy link

codecov bot commented Feb 5, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 69.94%. Comparing base (7f6b060) to head (6164abb).
⚠️ Report is 492 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #406      +/-   ##
==========================================
+ Coverage   64.59%   69.94%   +5.35%     
==========================================
  Files         154      185      +31     
  Lines        6064     7394    +1330     
==========================================
+ Hits         3917     5172    +1255     
+ Misses       2064     2025      -39     
- Partials       83      197     +114     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

- Remove unused serializeSchema import from PayloadFooter
- Destructure props directly in PayloadFooter to match PayloadSEO pattern
- Conditionally render og:url only when meta.canonical is present
- Add minor changeset for sveltekit-helper

https://claude.ai/code/session_01ShF8qREiHw7gTnmaxSTk99
@claude
Copy link

claude bot commented Feb 6, 2026

Review summary

  • Overall score: 6/10
  • Critical issues: 1
  • Warnings: 3
  • Suggestions: 3
  • Recommendation: ⚠️ Approve with comments

This PR adds comprehensive SEO and footer code injection components for Payload CMS integration. While the implementation is functional and well-structured, there is a critical XSS vulnerability from using {@html} without sanitisation, and several areas where test coverage and type safety could be improved.

Critical issues 🔴

1. XSS vulnerability from unsanitised HTML injection

Location: PayloadSEO.svelte:177,181,185,189,193 and PayloadFooter.svelte:29,33

The components use {@html} to render user-provided content without any sanitisation. This creates a direct XSS vulnerability where malicious scripts in Payload CMS settings could execute in users' browsers.

<!-- Vulnerable code -->
{@html serializeSchema(meta.structuredData.settings)}
{@html codeInjectionSettings.head}
{@html siteCodeInjection}

Impact: If an attacker gains access to Payload CMS admin (or if admin accounts are compromised), they could inject malicious JavaScript that executes on all pages, stealing user credentials, session tokens, or performing actions on behalf of users.

Recommendation:

  • Add input validation/sanitisation for code injection fields in Payload CMS configuration
  • Document the security implications clearly in component documentation
  • Consider using a Content Security Policy (CSP) to mitigate XSS risks
  • At minimum, add prominent security warnings in the component JSDoc comments
Example security documentation
<!--
  @component
  
  PayloadSEO renders all head meta tags for a page.
  
  ⚠️ SECURITY WARNING: This component renders HTML from CMS settings without 
  sanitisation. Only grant Payload CMS admin access to trusted users. 
  Malicious code injection could lead to XSS attacks.
  
  @example
  ```svelte
  <PayloadSEO siteName="My Site" settings={globalSettings} pageMeta={page.meta} />

-->

</details>

## Warnings 🟡

### 1. Insufficient test coverage for core components

**Location**: Test coverage only exists for `serializeSchema` utility

The PR adds two significant components (`PayloadSEO.svelte`, `PayloadFooter.svelte`) with complex logic for metadata merging, schema generation, and fallback chains, but provides no tests for these components. According to AGENTS.md, comprehensive test coverage is expected.

**Recommendation**: Add vitest tests covering:
- Metadata fallback chain behaviour (page meta → settings meta → defaults)
- Schema generation with various input combinations
- Edge cases like missing/null values
- Social link filtering and Twitter handle formatting

### 2. Missing type safety for structured data

**Location**: `types.ts:71`

```typescript
structuredData?: unknown;

The structuredData field uses unknown type, which provides no type safety or autocomplete for consumers using structured data schemas.

Recommendation: Define proper types for JSON-LD schemas or at least use a more descriptive type:

// Option 1: Specific schemas
type StructuredData = WebSiteSchema | OrganizationSchema | BreadcrumbSchema | /* ... */;

// Option 2: Generic but documented
type StructuredData = Record<string, unknown> & {
  '@context': string;
  '@type': string;
};

3. Potential runtime error from number to string conversion

Location: PayloadSEO.svelte:151,154

<meta property="og:image:width" content={meta.media.width.toString()} />
<meta property="og:image:height" content={meta.media.height.toString()} />

If meta.media.width or meta.media.height are null (allowed by the type definition), calling .toString() will throw a runtime error.

Recommendation: Add null checks or use optional chaining:

{#if meta.media.width}
  <meta property="og:image:width" content={meta.media.width.toString()} />
{/if}

Suggestions 🟢

1. Inconsistent British English usage

Location: ld-json.ts:3 and ld-json.test.ts:5

The code uses "serialises" (British) in comments but the function name is serializeSchema (American). According to AGENTS.md, British spellings should be used throughout. Consider renaming to serialiseSchema for consistency, or accept American spellings for code identifiers while keeping British in documentation.

2. Missing JSDoc for exported utilities

Location: resolve.ts:8

The resolveItems function has good implementation documentation but could benefit from usage examples in the JSDoc, especially around when it returns null vs the resolved object.

3. Component props could be more defensive

Location: PayloadSEO.svelte:34-38

The component uses optional chaining but could handle edge cases more explicitly. For example, settings.siteName?.trim() could return an empty string which might not be the intended fallback.

Recommendation:

const site = $derived(settings.siteName?.trim() || siteName);

Additional notes:

  • The PR correctly follows the changeset convention
  • Package.json exports are properly configured
  • Code style follows Svelte 5 patterns with runes
  • Component documentation is clear and helpful

@ainsleyclark ainsleyclark merged commit 69e11e6 into main Feb 6, 2026
6 checks passed
@ainsleyclark ainsleyclark deleted the claude/add-payload-seo-components-odyVR branch February 6, 2026 16:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants