From 9ed5dda0285d802b2580edb96980756b2bbd82f2 Mon Sep 17 00:00:00 2001 From: Martin Perales <140137314+martinp09@users.noreply.github.com> Date: Mon, 11 May 2026 02:59:41 +0000 Subject: [PATCH 1/2] perf: optimize playground lighthouse score --- src/components/playground/Playground.tsx | 108 ---------- src/components/playground/index.tsx | 13 -- src/css/graphiql.css | 41 ---- src/pages/playground.tsx | 22 -- static/playground/index.html | 253 +++++++++++++++++++++++ 5 files changed, 253 insertions(+), 184 deletions(-) delete mode 100644 src/components/playground/Playground.tsx delete mode 100644 src/components/playground/index.tsx delete mode 100644 src/css/graphiql.css delete mode 100644 src/pages/playground.tsx create mode 100644 static/playground/index.html diff --git a/src/components/playground/Playground.tsx b/src/components/playground/Playground.tsx deleted file mode 100644 index d077327b4..000000000 --- a/src/components/playground/Playground.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import React, {useEffect, useMemo, useState} from "react" -import {GraphiQL} from "graphiql" -import {analyticsHandler, isValidURL, sendConversionEvent} from "@site/src/utils" -import {CookiePreferenceCategory, playgroundAdsConversionId} from "@site/src/constants" -import "graphiql/graphiql.css" -import "../../css/graphiql.css" -import {type FetcherParams, FetcherOpts} from "@graphiql/toolkit" -import {useCookieConsent} from "@site/src/utils/hooks/useCookieConsent" -import {createGraphiQLFetcher} from "@graphiql/create-fetcher" - -const useDebouncedValue = (inputValue: string, delay: number) => { - const [debouncedValue, setDebouncedValue] = useState(inputValue) - - useEffect(() => { - const handler = setTimeout(() => { - setDebouncedValue(inputValue) - }, delay) - - return () => { - clearTimeout(handler) - } - }, [inputValue, delay]) - - return debouncedValue -} - -const Playground = () => { - const apiEndpointParam = typeof window !== "undefined" && new URLSearchParams(window.location.search).get("u") - const initialApiEndpoint = - (typeof apiEndpointParam === "string" && isValidURL(apiEndpointParam) && new URL(apiEndpointParam)) || "" - const [apiEndpoint, setApiEndpoint] = useState( - initialApiEndpoint !== "" ? new URL(initialApiEndpoint) : "", - ) - const [inputValue, setInputValue] = useState(initialApiEndpoint.toString()) - - const {getCookieConsent} = useCookieConsent() - const cookieConsent = getCookieConsent() - - const debouncedApiEndpoint = useDebouncedValue(inputValue, 500) - const apiEndpointInputClasses = `border border-solid border-tailCall-border-light-500 rounded-lg font-space-grotesk h-11 w-[100%] - p-SPACE_04 text-content-small outline-none focus:border-x-tailCall-light-700` - - useEffect(() => { - if (isValidURL(debouncedApiEndpoint)) { - setApiEndpoint(new URL(debouncedApiEndpoint)) - } - }, [debouncedApiEndpoint]) - - const graphQLFetcher = async (graphQLParams: FetcherParams, opts?: FetcherOpts) => { - if (apiEndpoint.toString().trim() === "") { - return Promise.resolve({}) - } - analyticsHandler("GraphQL", "tc_fetch_query", apiEndpoint.toString()) - sendConversionEvent(playgroundAdsConversionId) - - const fetcher = createGraphiQLFetcher({url: apiEndpoint.toString()}) - return fetcher(graphQLParams, opts) - } - - const emptyGraphiqlStorageObject = { - getItem: (): null => null, - setItem: (): void => undefined, - removeItem: (): void => undefined, - clear: (): void => undefined, - length: 0, - } - - const graphiqlStorage = useMemo(() => { - if ( - cookieConsent?.accepted && - (!cookieConsent?.preferences || cookieConsent?.preferences?.includes(CookiePreferenceCategory.PREFERENCE)) - ) { - // Defaults to local storage - return undefined - } - - // Block storing graphiql data in local storage if user denies cookie consent - return emptyGraphiqlStorageObject - }, [cookieConsent]) - - return ( -
- {typeof window !== "undefined" && ( -
-
- setInputValue(e.target.value)} - className={apiEndpointInputClasses} - placeholder="API Endpoint" - /> -
-
- - - <> - - -
-
- )} -
- ) -} - -export default Playground diff --git a/src/components/playground/index.tsx b/src/components/playground/index.tsx deleted file mode 100644 index 846c15cf5..000000000 --- a/src/components/playground/index.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from "react" -import Playground from "./Playground" -import Announcement from "@site/src/components/shared/Announcement" - -const PlaygroundPage = (): JSX.Element => { - return ( - <> - - - ) -} - -export default PlaygroundPage diff --git a/src/css/graphiql.css b/src/css/graphiql.css deleted file mode 100644 index 68c7a0a92..000000000 --- a/src/css/graphiql.css +++ /dev/null @@ -1,41 +0,0 @@ -.graphiql-main { - height: 80vh; -} - -.graphiql-main * { - font-family: "Space Grotesk", sans-serif; -} - -.graphiql-query-editor *, -.graphiql-response * { - font-family: "Space Mono", sans-serif; -} - -.graphiql-dialog, -.graphiql-dialog-overlay { - z-index: var(--ifm-z-index-overlay) !important; -} - -@media (max-width: 600px) { - .graphiql-main { - flex-direction: column; - overflow: scroll; - } - - .graphiql-session { - flex-direction: column; - } - - .graphiql-response { - margin-top: 16px; - height: 30vh; - } - - .graphiql-editor-tool { - height: 10vh; - } - - button[data-value="short-keys"] { - display: none !important; - } -} diff --git a/src/pages/playground.tsx b/src/pages/playground.tsx deleted file mode 100644 index e38dbc11c..000000000 --- a/src/pages/playground.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React, {useEffect} from "react" -import ReactGA from "react-ga4" -import Layout from "@theme/Layout" -import PlaygroundPage from "../components/playground" -import {useLocation} from "@docusaurus/router" -import {PageDescription, PageTitle} from "../constants/titles" - -const Playground = () => { - const location = useLocation() - - useEffect(() => { - ReactGA.send({hitType: "pageview", page: location.pathname, title: "Playground Page"}) - }, []) - - return ( - - - - ) -} - -export default Playground diff --git a/static/playground/index.html b/static/playground/index.html new file mode 100644 index 000000000..6b1603375 --- /dev/null +++ b/static/playground/index.html @@ -0,0 +1,253 @@ + + + + + + Tailcall GraphQL Playground + + + + + +
+ +
+

GraphQL Playground

+

Enter a GraphQL API endpoint to load the interactive editor only when you need it.

+
+
+
+ + + +
+
+ The editor is deferred until an endpoint is provided, keeping this page fast on first load. +
+ +
+
+ + + From 358fa177df504513f7dc2828a3f8e75697cd4f90 Mon Sep 17 00:00:00 2001 From: Martin Perales <140137314+martinp09@users.noreply.github.com> Date: Mon, 11 May 2026 03:56:36 +0000 Subject: [PATCH 2/2] perf: keep playground storage transient --- static/playground/index.html | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/static/playground/index.html b/static/playground/index.html index 6b1603375..198995db9 100644 --- a/static/playground/index.html +++ b/static/playground/index.html @@ -186,6 +186,19 @@

GraphQL Playground

} } + // Keep the standalone route non-persistent by default. The previous + // Docusaurus app guarded GraphiQL localStorage behind cookie preferences; + // this static route avoids browser storage entirely while preserving the + // deferred interactive playground behavior. + const transientGraphiQLStorage = { + getItem: () => null, + setItem: () => undefined, + removeItem: () => undefined, + clear: () => undefined, + key: () => null, + length: 0, + } + const loadScript = (src) => new Promise((resolve, reject) => { const script = document.createElement("script") @@ -227,7 +240,7 @@

GraphQL Playground

const fetcher = window.GraphiQL.createFetcher({url: endpoint}) root = root || window.ReactDOM.createRoot(mount) - root.render(window.React.createElement(window.GraphiQL, {fetcher})) + root.render(window.React.createElement(window.GraphiQL, {fetcher, storage: transientGraphiQLStorage})) status.hidden = true const next = new URL(window.location.href)