Skip to content

Instantly share code, notes, and snippets.

@WomB0ComB0
Created September 6, 2025 14:57
Show Gist options
  • Select an option

  • Save WomB0ComB0/eae18f8431f979a35dbd48dc5c23b4f1 to your computer and use it in GitHub Desktop.

Select an option

Save WomB0ComB0/eae18f8431f979a35dbd48dc5c23b4f1 to your computer and use it in GitHub Desktop.
speculation-intersection - Enhanced with AI-generated documentation
import { useCallback, useEffect, useRef } from "react";
import { flushSync } from "react-dom";
import { app } from "~/constants";
import { Stringify } from "~/utils/helpers";
import { generateSchema } from "~/utils/schema";
/**
* Configuration type for preload/prefetch behavior
* @typedef {Object} PreloadConfig
* @property {string[]} prerenderPaths - Paths to prerender eagerly for instant navigation
* @property {string[]} prefetchPaths - Paths to prefetch in the background
* @property {string[]} excludePaths - Paths to exclude from preloading
*/
type PreloadConfig = {
prerenderPaths: string[];
prefetchPaths: string[];
excludePaths: string[];
};
export const Scripts = () => {
const schemaOrg = {
"@context": "https://schema.org",
"@type": "Organization",
name: app.name,
url: app.url,
image: app.logo,
logo: app.logo,
sameAs: [
"https://www.example.com",
],
} as const;
/**
* Configuration object defining paths for preloading behavior
*/
const config: PreloadConfig = {
prerenderPaths: ["/"],
prefetchPaths: ["/legal/*"],
excludePaths: ["/api/*"],
};
/**
* Ref to store the intersection observer instance
*/
const observerRef = useRef<IntersectionObserver | null>(null);
/**
* Ref to track which links have already had speculation rules added
*/
const speculationScriptsRef = useRef<Set<string>>(new Set());
/**
* Prevents default context menu behavior
* @param {MouseEvent} event - The context menu event
*/
const handleContextMenu = useCallback((event: MouseEvent) => {
event.preventDefault();
}, []);
/**
* Handles view transitions between pages using the View Transitions API
* Adds and removes transition classes for animation
*/
const handleViewTransition = useCallback(() => {
if (!document.startViewTransition) return;
document.startViewTransition(() => {
flushSync(() => {
document.body.classList.add("view-transition-group");
document.body.classList.remove("view-transition-group");
});
});
}, []);
/**
* Creates a speculation rules script element
* @param {object} rules - The speculation rules to apply
* @returns {HTMLScriptElement} The created script element
*/
const createSpeculationScript = useCallback((rules: object) => {
const script = document.createElement("script");
script.type = "speculationrules";
script.text = Stringify(rules);
return script;
}, []);
/**
* Adds dynamic speculation rules for a specific link
* @param {string} link - The URL to add speculation rules for
*/
const addDynamicSpeculation = useCallback(
(link: string) => {
if (speculationScriptsRef.current.has(link)) return;
const rules = {
prefetch: [
{
source: "list",
urls: [link],
},
],
};
const script = createSpeculationScript(rules);
document.head.appendChild(script);
speculationScriptsRef.current.add(link);
},
[createSpeculationScript],
);
/**
* Handles intersection observer entries
* Adds speculation rules for links as they become visible
* @param {IntersectionObserverEntry[]} entries - The intersection entries to process
*/
const handleIntersection = useCallback(
(entries: IntersectionObserverEntry[]) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const link = entry.target.getAttribute("href");
if (link?.startsWith("/")) {
addDynamicSpeculation(link);
}
}
});
},
[addDynamicSpeculation],
);
/**
* Sets up intersection observer, event listeners, and cleanup
* - Initializes intersection observer for link prefetching
* - Adds event listeners for context menu and navigation
* - Cleans up observers and listeners on unmount
*/
useEffect(() => {
observerRef.current = new IntersectionObserver(handleIntersection, {
threshold: 0.1,
rootMargin: "50px",
});
const links = document.querySelectorAll('a[href^="/"]');
links.forEach((link) => observerRef.current?.observe(link));
document.addEventListener("contextmenu", handleContextMenu);
window.addEventListener("navigate", handleViewTransition);
return () => {
observerRef.current?.disconnect();
document.removeEventListener("contextmenu", handleContextMenu);
window.removeEventListener("navigate", handleViewTransition);
speculationScriptsRef.current.forEach((link) => {
const script = document.querySelector(
`script[data-speculation="${link}"]`,
);
script?.remove();
});
};
}, [handleContextMenu, handleIntersection, handleViewTransition]);
/**
* Base speculation rules configuration for prerendering and prefetching
* Defines rules for:
* - Prerendering specific paths with different eagerness levels
* - Prefetching paths and patterns with conditions
* - Excluding certain paths from speculation
*/
const baseSpeculationRules = {
prerender: [
{
source: "list",
urls: config.prerenderPaths,
eagerness: "moderate",
},
{
where: {
and: [
{ href_matches: "/*" },
...config.excludePaths.map((path) => ({
not: { href_matches: path },
})),
],
},
eagerness: "conservative",
},
],
prefetch: [
{
source: "list",
urls: config.prefetchPaths,
},
{
where: {
and: [
{ href_matches: "/resources/*" },
{ not: { selector_matches: "[data-no-prefetch]" } },
],
},
eagerness: "conservative",
},
],
} as const;
const organizationSchema = generateSchema({
type: "Organization",
name: app.name,
url: app.url,
thumbnailUrl: app.logo,
logo: app.logo,
sameAs: [
"https://www.example.com/",
],
});
return (
<>
<script
id="speculation-rules"
strategy="beforeInteractive"
type="speculationrules"
dangerouslySetInnerHTML={{
__html: Stringify(baseSpeculationRules),
}}
onError={(event) => {
console.error("Error loading speculation rules script:", event);
}}
/>
<script
type="application/ld+json"
id="schema-org"
strategy="afterInteractive"
dangerouslySetInnerHTML={{
__html: Stringify(schemaOrg),
}}
onError={(event) => {
console.error("Error loading Schema.org script:", event);
}}
/>
<script
type="application/ld+json"
id="schema-org-extended"
strategy="afterInteractive"
dangerouslySetInnerHTML={{
__html: Stringify(organizationSchema),
}}
onError={(event) => {
console.error("Error loading Schema.org extended script:", event);
}}
/>
</>
);
};
Scripts.displayName = "Scripts";
export default Scripts;

speculation-intersection.tsx

File Type: TSX
Lines: 256
Size: 6.4 KB
Generated: 9/6/2025, 10:57:55 AM


Code Analysis: speculation-intersection.tsx

This React component, Scripts, is responsible for injecting various scripts into the document head to enhance performance and SEO. It leverages the Speculation Rules API for prefetching and prerendering, manages view transitions, and injects schema.org structured data.

Key Functionality:

  1. Speculation Rules API Integration:

    • Generates and injects a <script type="speculationrules"> tag containing JSON-formatted speculation rules. These rules tell the browser which URLs to prefetch or prerender based on defined patterns and conditions.
    • Uses baseSpeculationRules to define static rules for prerendering and prefetching based on the config object. The config object allows specifying prerenderPaths, prefetchPaths, and excludePaths.
    • Implements dynamic speculation rules using an IntersectionObserver. When a link within the viewport (threshold 0.1, root margin 50px) is observed, a new <script type="speculationrules"> tag is created and injected to prefetch that specific link. This is managed by addDynamicSpeculation and handleIntersection.
    • A speculationScriptsRef (using useRef) tracks which links have already had speculation rules added to prevent duplicates.
  2. View Transitions API:

    • Uses the View Transitions API (if available) to create smoother page transitions.
    • The handleViewTransition function adds and removes CSS classes (view-transition-group) to the document.body during the transition, allowing for custom animations.
  3. Schema.org Integration:

    • Injects <script type="application/ld+json"> tags containing schema.org structured data for SEO.
    • Includes basic organization schema and extended organization schema generated by generateSchema.
  4. Context Menu Handling:

    • Prevents the default context menu behavior using handleContextMenu.

React Hooks and Techniques:

  • useRef: Used to store mutable values that persist across renders, specifically for the observerRef (IntersectionObserver instance) and speculationScriptsRef (Set of links with speculation rules).
  • useCallback: Used to memoize functions, preventing unnecessary re-renders and ensuring that event listeners and observers are not recreated on every render. Dependencies are carefully managed.
  • useEffect: Used to perform side effects, such as setting up the IntersectionObserver, adding event listeners, and cleaning up resources on unmount. The dependency array ensures that these effects are only executed when necessary.
  • flushSync: Used within handleViewTransition to ensure that DOM updates are applied synchronously before the view transition starts.
  • dangerouslySetInnerHTML: Used to inject the JSON-formatted speculation rules and schema.org data into the script tags.

Performance Considerations:

  • The use of IntersectionObserver for dynamic speculation rules helps to avoid prefetching links that are not visible to the user, improving performance.
  • The speculationScriptsRef prevents duplicate speculation rules from being added.
  • The strategy="beforeInteractive" and strategy="afterInteractive" attributes on the script tags control when the scripts are executed, optimizing page load performance.

Potential Improvements:

  • Error handling could be improved by providing more specific error messages and logging.
  • Consider adding a configuration option to disable the View Transitions API.
  • The sameAs array in the schema.org data could be made configurable.
  • The component could be made more generic by allowing the configuration of the IntersectionObserver options (threshold, rootMargin).
  • Consider using a more robust method for determining if the View Transitions API is supported.
  • The component could benefit from more comprehensive testing, including unit tests and integration tests.

Overall:

The speculation-intersection.tsx component is a well-structured and efficient way to integrate the Speculation Rules API, View Transitions API, and schema.org structured data into a React application. It demonstrates good use of React hooks and techniques to optimize performance and maintainability. The component is designed to improve the user experience by prefetching and prerendering links, providing smoother page transitions, and enhancing SEO.


Description generated using AI analysis

"use client";
type SchemaType =
| "NewsArticle"
| "WebPage"
| "Organization"
| "Article"
| "BlogPosting";
interface SchemaProps {
type: SchemaType;
headline?: string;
url: string;
thumbnailUrl?: string;
datePublished?: string;
articleSection?: string;
creator?: string | string[];
keywords?: string[];
name?: string;
logo?: string;
sameAs?: string[];
}
export const generateSchema = (props: SchemaProps) => {
const schema = {
"@context": "https://schema.org",
"@type": props.type,
...(props.headline && { headline: props.headline }),
url: props.url,
...(props.thumbnailUrl && { thumbnailUrl: props.thumbnailUrl }),
...(props.datePublished && { datePublished: props.datePublished }),
...(props.articleSection && { articleSection: props.articleSection }),
...(props.creator && { creator: props.creator }),
...(props.keywords && { keywords: props.keywords }),
...(props.name && { name: props.name }),
...(props.logo && { logo: props.logo }),
...(props.sameAs && { sameAs: props.sameAs }),
};
return schema;
};

types.ts

File Type: TS
Lines: 42
Size: 1 KB
Generated: 9/6/2025, 10:57:55 AM


Code Analysis: types.ts

This TypeScript file defines types and a function for generating schema.org JSON-LD schema objects, primarily for SEO purposes in a web application. It focuses on creating structured data that search engines can easily understand.

Purpose:

The primary goal of this file is to provide a type-safe way to generate schema.org compliant JSON-LD objects. These objects are used to enhance search engine visibility and provide rich snippets in search results.

Key Elements:

  • SchemaType type: A discriminated union type defining the supported schema types (e.g., NewsArticle, WebPage, Organization). This ensures that only valid schema types are used.
  • SchemaProps interface: Defines the properties required for generating a schema object. It includes common properties like headline, url, thumbnailUrl, datePublished, and more specific properties like creator, keywords, name, logo, and sameAs. The optional properties use the ? modifier, indicating they are not always required for every schema type.
  • generateSchema function: This function takes a SchemaProps object as input and returns a schema object. It constructs the schema object by conditionally including properties based on their presence in the props object. The spread operator (...) is used to merge properties into the base schema object.

Runtime & Dependencies:

  • TypeScript: The file is written in TypeScript and requires a TypeScript compiler to be transpiled into JavaScript.
  • "use client"; directive: This directive indicates that the code is intended to run on the client-side (browser). This is commonly used in React Server Components (RSC) to mark components that should be rendered in the browser.
  • No external dependencies: The file does not rely on any external libraries or modules besides the standard TypeScript library.

How it Works:

  1. Type Definitions: The SchemaType and SchemaProps define the structure and allowed values for creating schema objects.
  2. Schema Generation: The generateSchema function takes a SchemaProps object and constructs a JSON-LD schema object.
  3. Conditional Property Inclusion: The function uses the spread operator and conditional checks (...(props.headline && { headline: props.headline })) to include properties only if they are present in the props object. This allows for flexibility in creating different schema objects with varying properties.
  4. JSON-LD Structure: The function creates a JSON object with @context and @type properties, which are required for schema.org JSON-LD.

Usage:

This file is intended to be used within a web application (likely a React application, given the "use client"; directive) to generate schema.org JSON-LD objects. These objects can then be included in the <head> section of HTML pages to provide structured data to search engines.

Example usage:

import { generateSchema } from './types';

const schema = generateSchema({
  type: 'NewsArticle',
  headline: 'Breaking News!',
  url: 'https://example.com/news/breaking-news',
  datePublished: '2024-01-01',
  author: 'John Doe',
});

// Convert the schema object to a JSON string
const schemaString = JSON.stringify(schema);

// Include the schema string in the <head> section of the HTML page
// <script type="application/ld+json">{schemaString}</script>

Next Steps:

  • Validation: Add validation to ensure that the SchemaProps object contains valid data for the specified SchemaType. This could involve using a validation library like Zod or Yup.
  • More Schema Types: Expand the SchemaType union to include more schema.org types.
  • Type Safety: Improve type safety by using more specific types for certain properties, such as datePublished (e.g., a Date object or an ISO 8601 string).
  • Error Handling: Add error handling to gracefully handle invalid input.
  • Integration with a framework: Provide helper functions or components to easily integrate the schema generation process with a specific web framework (e.g., React, Next.js).
  • Testing: Implement unit tests to ensure that the generateSchema function generates correct schema objects for different input values.

Description generated using AI analysis

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment