Skip to content

fix: use page.data for dynamic SEO meta tags instead of duplicating t… - #3411

Draft
Adarshkumar0509 wants to merge 2 commits into
OWASP:masterfrom
Adarshkumar0509:fix/dynamic-seo-meta-page-data
Draft

fix: use page.data for dynamic SEO meta tags instead of duplicating t…#3411
Adarshkumar0509 wants to merge 2 commits into
OWASP:masterfrom
Adarshkumar0509:fix/dynamic-seo-meta-page-data

Conversation

@Adarshkumar0509

Copy link
Copy Markdown
Collaborator

Description

This PR fixes the duplicate SEO meta tag issue that was causing problems with SSR and social media scrapers.

Previously, each page was managing its own meta tags inside svelte:head, which led to duplicate og:title, og:description, twitter:title etc. showing up in the raw HTML. Social platforms like Twitter and LinkedIn are picky about this and would pick up the wrong tag.

What I did:

Moved all dynamic meta tags into metadata.svelte it now reads title, description, url, and ogType from $page.data with sensible fallbacks to the layout's translation keys
Updated news/[slug]/+page.server.ts to return these values from the load function so the layout picks them up automatically
Removed the svelte:head block from news/[slug]/+page.svelte since the layout now handles everything no more duplicates

This is the approach sydseter suggested in the comments on #2946.

Resolves: #2194

AI Tool Disclosure

  • My contribution does not include any AI-generated content
  • My contribution includes AI-generated content, as disclosed below:
    • AI Tools: [e.g. GitHub CoPilot, ChatGPT, JetBrains Junie etc.]
    • LLMs and versions: [e.g. GPT-4.1, Claude Haiku 4.5, Gemini 2.5 Pro etc.]
    • Prompts: [Summarize the key prompts or instructions given to the AI tools]

Affirmation

@Adarshkumar0509
Adarshkumar0509 marked this pull request as draft August 27, 2026 01:56
…hem per page

Signed-off-by: Adarsh Kumar <adarshkumarthakur0509@gmail.com>
@Adarshkumar0509
Adarshkumar0509 force-pushed the fix/dynamic-seo-meta-page-data branch from 3d14b52 to b8acbbc Compare August 27, 2026 09:48
@sydseter

sydseter commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

This will duplicate the description field. In order to fix that, you need to address the issue with the metadata field being duplicated across the codebase.

Problem

Route components currently repeat the same <svelte:head> structure for the
title, description, keywords, canonical URL, Open Graph tags, and Twitter tags.
Several routes also copy metadata from a different route (for example,
/copi, /how-to-play, and /roadmap), and dynamic routes use canonical URLs
that do not identify the complete resource.

What to do about it?

Replace the fixed social-only implementation in
metadata.svelte
with the single renderer for shared metadata. It should accept a typed,
complete metadata object:

type PageMetadata = {
    title: string;
    description: string;
    keywords: string;
    canonicalUrl: string;
    type?: "website" | "article";
    imageUrl?: string;
};

The component must render the title, canonical link, description, keywords,
Open Graph fields, and Twitter fields from this object. It must not import
$app/stores, read window, or derive a URL in a client-side effect.

Each +page.server.ts should create this object during its load function
and return it as metadata (this is simlar to what you have done so far). The root +layout.server.ts
can provide shared constants such as the canonical origin and default image,
but route loads must supply the route's final pathname and content-specific
title and description. Since these values are returned by load, SvelteKit
renders them into the static HTML during prerendering.

Each +page.svelte should replace its whole <svelte:head> metadata block
with:

<Metadata metadata={data.metadata} />

Structured data that is unique to a page, such as the home page's JSON-LD,
can remain in that route's <svelte:head> block.

Required changes

Migrate the remaining route heads to the same component:

Route Title / description source Canonical URL
/news news.head.* /news
/source source.head.* /source
/roadmap roadmap.head.* /roadmap
/swags swags.head.* /swags
/tribute tribute.head.* /tribute
/taxonomy taxonomy.head.* /taxonomy
/webshop webshop.head.* /webshop
/author/:name OWASP Cornucopia - {author.name} /author/{encoded name}
/news/:slug blog post title and excerpt /news/{encoded slug}
/taxonomy/*path formatted taxonomy title /taxonomy/{encoded path}
/cards/:card card title and card.desc /cards/{card.id}
/edition/:edition edition title and description /edition/{edition}
/edition/:edition/:card card title and card.desc /edition/{edition}/{card.id}
/edition/:edition/:card/:version card title and card.desc /edition/{edition}/{card.id}/{version}
/edition/:edition/:card/:version/:lang card title and card.desc /edition/{edition}/{card.id}/{version}/{lang}

Dynamic URLs must be assembled at prerender time from trusted route parameters
with encodeURIComponent (or the framework's server-safe route resolver),
never by accepting an arbitrary URL from page data. The route parameter should
remain in the canonical URL even when the rendered card or post is not found;
in that case the page should use the existing not-found behavior and a safe
generic description.

The /404 page should use the shared component with a generic title and
description and should not canonicalize to /, since that creates duplicate
index metadata.

Validation

  1. Run pnpm check and pnpm lint from cornucopia.owasp.org.
  2. Run the static-adapter production build, then inspect generated HTML for every route in the
    table.
  3. Assert each generated document has exactly one title, one description,
    one canonical link, one og:url, and one Twitter URL.
  4. Verify the generated HTML contains the final metadata before JavaScript is
    loaded or executed.
  5. Verify dynamic canonical URLs remain unique for cards, editions, taxonomy
    paths, authors, and news posts, including names/slugs containing reserved
    URL characters.

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.

Redesign for cornucopia.owasp.org

2 participants