From 77196668de835c9d5f69a369bea0ee95cee43e35 Mon Sep 17 00:00:00 2001 From: Juan Cruz Martinez Date: Wed, 22 Apr 2026 18:10:13 +0200 Subject: [PATCH] Add FAQPage schema, Organization schema, and llms.txt - Implement FAQPage structured data on /introduction with 7 Q&A items (EN/JA) - Add Organization schema with Auth0 sameAs links (Twitter, Facebook, LinkedIn) - Create llms.txt for AI agent discoverability with JWT skill installation info - Exclude llms.txt from i18n middleware routing Co-Authored-By: Claude Opus 4.6 --- public/llms.txt | 42 +++++++++++++++++++ .../introduction-page.component.tsx | 8 +++- .../dictionaries/introduction/en.ts | 39 +++++++++++++++++ .../dictionaries/introduction/ja.ts | 40 ++++++++++++++++++ .../models/introduction-dictionary.model.ts | 10 +++-- .../components/structured-data.component.tsx | 6 +++ .../seo/constants/organizations.constants.ts | 16 +++++++ src/features/seo/models/faq-metadata.model.ts | 8 ++++ .../seo/models/faq-structured-data.model.ts | 3 ++ .../organization-structured-data.model.ts | 3 ++ .../seo/services/structured-data.service.ts | 25 ++++++++++- src/middleware.ts | 2 +- 12 files changed, 195 insertions(+), 7 deletions(-) create mode 100644 public/llms.txt create mode 100644 src/features/seo/constants/organizations.constants.ts create mode 100644 src/features/seo/models/faq-metadata.model.ts create mode 100644 src/features/seo/models/faq-structured-data.model.ts create mode 100644 src/features/seo/models/organization-structured-data.model.ts diff --git a/public/llms.txt b/public/llms.txt new file mode 100644 index 00000000..8c46daea --- /dev/null +++ b/public/llms.txt @@ -0,0 +1,42 @@ +# jwt.io + +> JWT.IO allows you to decode, verify, and generate JSON Web Tokens. + +## About + +jwt.io is the go-to resource for JSON Web Tokens (JWTs). It provides: +- An interactive JWT debugger to decode and verify tokens +- A comprehensive library directory for JWT implementations +- Educational content explaining JWT concepts + +Maintained by Auth0. + +## Quick Links + +- Debugger: https://jwt.io +- Libraries: https://jwt.io/libraries +- Introduction: https://jwt.io/introduction + +## JWT Overview + +A JSON Web Token consists of three Base64Url-encoded parts: +1. Header - algorithm and token type +2. Payload - claims (registered, public, private) +3. Signature - ensures token integrity + +Format: xxxxx.yyyyy.zzzzz + +## For AI Agents + +Claude Code users can install JWT skills for token operations: +- `/jwt-decode` - Decode and inspect JWTs +- `/jwt-encode` - Create and sign JWTs +- `/jwt-validate` - Verify JWT signatures and claims + +Install: `npx skills add jsonwebtoken/jwt-skills` +Repository: https://github.com/jsonwebtoken/jwt-skills + +## Resources + +- RFC 7519: https://datatracker.ietf.org/doc/html/rfc7519 +- Source: https://github.com/jsonwebtoken/jsonwebtoken.github.io diff --git a/src/features/introduction/components/introduction-page/introduction-page.component.tsx b/src/features/introduction/components/introduction-page/introduction-page.component.tsx index 17ed41fe..0d8609bc 100644 --- a/src/features/introduction/components/introduction-page/introduction-page.component.tsx +++ b/src/features/introduction/components/introduction-page/introduction-page.component.tsx @@ -1,10 +1,14 @@ import React from "react"; import { StructuredData } from "@/features/seo/components/structured-data.component"; -import { generateArticleStructuredData } from "@/features/seo/services/structured-data.service"; +import { + generateArticleStructuredData, + generateFaqStructuredData, +} from "@/features/seo/services/structured-data.service"; import { Auth0CtaComponent } from "@/features/common/components/auth0-cta/auth0-cta.component"; import { getIntroductionDictionary } from "@/features/localization/services/language-dictionary.service"; import { IntroductionArticleComponent } from "@/features/introduction/components/introduction-article/introduction-article.component"; import { getAuth0Dictionary } from "@/features/localization/services/ui-language-dictionary.service"; +import { AUTH0_ORGANIZATION } from "@/features/seo/constants/organizations.constants"; interface IntroductionPageComponentProps { languageCode: string; @@ -115,6 +119,8 @@ export const IntroductionPageComponent: React.FC< datePublished: introductionDictionary.metadata.datePublished, dateModified: introductionDictionary.metadata.dateModified, }), + AUTH0_ORGANIZATION, + generateFaqStructuredData(introductionDictionary.faq), ]} /> ; } diff --git a/src/features/seo/constants/organizations.constants.ts b/src/features/seo/constants/organizations.constants.ts new file mode 100644 index 00000000..13add141 --- /dev/null +++ b/src/features/seo/constants/organizations.constants.ts @@ -0,0 +1,16 @@ +import { OrganizationStructuredDataModel } from "@/features/seo/models/organization-structured-data.model"; + +export const AUTH0_ORGANIZATION: OrganizationStructuredDataModel = { + "@context": "https://schema.org", + "@type": "Organization", + name: "Auth0", + legalName: "Auth0 Inc.", + url: "https://auth0.com/", + logo: "https://cdn.auth0.com/website/assets/pages/press/img/resources/auth0-logo-main-6001cece68.svg", + foundingDate: "2013", + sameAs: [ + "https://twitter.com/auth0", + "https://www.facebook.com/getauth0/", + "https://www.linkedin.com/company/auth0", + ], +}; diff --git a/src/features/seo/models/faq-metadata.model.ts b/src/features/seo/models/faq-metadata.model.ts new file mode 100644 index 00000000..74133c28 --- /dev/null +++ b/src/features/seo/models/faq-metadata.model.ts @@ -0,0 +1,8 @@ +export interface FaqItemMetadataModel { + question: string; + answer: string; +} + +export interface FaqMetadataModel { + items: FaqItemMetadataModel[]; +} diff --git a/src/features/seo/models/faq-structured-data.model.ts b/src/features/seo/models/faq-structured-data.model.ts new file mode 100644 index 00000000..08b46244 --- /dev/null +++ b/src/features/seo/models/faq-structured-data.model.ts @@ -0,0 +1,3 @@ +import { FAQPage, WithContext } from "schema-dts"; + +export type FaqStructuredDataModel = WithContext; diff --git a/src/features/seo/models/organization-structured-data.model.ts b/src/features/seo/models/organization-structured-data.model.ts new file mode 100644 index 00000000..2f97ba86 --- /dev/null +++ b/src/features/seo/models/organization-structured-data.model.ts @@ -0,0 +1,3 @@ +import { Organization, WithContext } from "schema-dts"; + +export type OrganizationStructuredDataModel = WithContext; diff --git a/src/features/seo/services/structured-data.service.ts b/src/features/seo/services/structured-data.service.ts index 976bf50f..5b67d9fc 100644 --- a/src/features/seo/services/structured-data.service.ts +++ b/src/features/seo/services/structured-data.service.ts @@ -2,9 +2,11 @@ import { ArticleStructuredDataModel } from "@/features/seo/models/article-struct import { ArticleMetadataModel } from "@/features/seo/models/article-metadata.model"; import { HowToStructuredDataModel } from "@/features/seo/models/how-to-structured-data.model"; import { HowToMetadataModel } from "@/features/seo/models/how-to-metadata.model"; -import { HowToStep, ListItem } from "schema-dts"; +import { HowToStep, ListItem, Question } from "schema-dts"; import { BreadcrumbMetadataModel } from "@/features/seo/models/breadcrumb-metadata.model"; import { BreadcrumbStructuredDataModel } from "@/features/seo/models/breadcrumb-structured-data.model"; +import { FaqMetadataModel } from "@/features/seo/models/faq-metadata.model"; +import { FaqStructuredDataModel } from "@/features/seo/models/faq-structured-data.model"; import { siteTree } from "@/features/seo/site-tree"; import { createUrlPath } from "@/libs/utils/path.utils"; @@ -73,3 +75,24 @@ export const generateBreadcrumbStructuredData = ( itemListElement: itemList, }; }; + +export const generateFaqStructuredData = ( + faq: FaqMetadataModel, +): FaqStructuredDataModel => { + const mainEntity: Question[] = faq.items.map((item) => { + return { + "@type": "Question", + name: item.question, + acceptedAnswer: { + "@type": "Answer", + text: item.answer, + }, + }; + }); + + return { + "@context": "https://schema.org", + "@type": "FAQPage", + mainEntity: mainEntity, + }; +}; diff --git a/src/middleware.ts b/src/middleware.ts index 5d321396..f4f0be95 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -159,7 +159,7 @@ export function middleware(request: NextRequest) { export const config = { matcher: [ - "/((?!api/|favicon.ico|sitemap.xml|robots.txt|google30e29a6679a06e08.html|manifest.webmanifest|_next/static|_next/image|diagrams/|icons/|images/|img/|apple-icon/|icon/).*)", + "/((?!api/|favicon.ico|sitemap.xml|robots.txt|llms.txt|google30e29a6679a06e08.html|manifest.webmanifest|_next/static|_next/image|diagrams/|icons/|images/|img/|apple-icon/|icon/).*)", "/", ], };