Skip to content

Commit 107d260

Browse files
committed
Improve performance using Partytown to offload Ads and GA scripts to separate thread
1 parent 67a63ff commit 107d260

File tree

12 files changed

+10934
-3907
lines changed

12 files changed

+10934
-3907
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,6 @@ public/wiki
4646
/scripts/*.json
4747
/.claude
4848
vali-data/*
49-
mapgens/*
49+
mapgens/*
50+
# Partytown (generated from node_modules)
51+
public/~partytown/

components/headContent.js

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import Head from "next/head";
22
import { useEffect } from "react";
33

44
export default function HeadContent({ text, inCoolMathGames, inCrazyGames = false }) {
5-
65
useEffect(() => {
76
if (!window.location.search.includes("crazygames") && !process.env.NEXT_PUBLIC_POKI &&
87
!process.env.NEXT_PUBLIC_COOLMATH) {
@@ -15,18 +14,20 @@ export default function HeadContent({ text, inCoolMathGames, inCrazyGames = fals
1514
// document.body.appendChild(scriptAp);
1615
// end adinplay script
1716

18-
// start nitroPay script - defer to after page load completes
17+
// start nitroPay script - runs in Web Worker via Partytown
1918
window.nitroAds=window.nitroAds||{createAd:function(){return new Promise(e=>{window.nitroAds.queue.push(["createAd",arguments,e])})},addUserToken:function(){window.nitroAds.queue.push(["addUserToken",arguments])},queue:[]};
2019

2120
const loadNitroAds = () => {
2221
if (document.querySelector('script[src*="nitropay.com"]')) return;
2322
const script = document.createElement('script');
2423
script.src = "https://s.nitropay.com/ads-2071.js";
25-
script.async = true;
24+
script.type = "text/partytown"; // Run in Web Worker
2625
document.head.appendChild(script);
26+
// Notify Partytown to process dynamically added script
27+
window.dispatchEvent(new CustomEvent('ptupdate'));
2728
};
2829

29-
// Wait for page load event (ensures fonts/LCP complete), then defer further
30+
// Wait for page load event (ensures fonts/LCP complete), then load in worker
3031
const scheduleAdLoad = () => {
3132
if ('requestIdleCallback' in window) {
3233
requestIdleCallback(loadNitroAds, { timeout: 3000 });
@@ -123,21 +124,12 @@ ads.js"></script>*/
123124
<meta name="google-site-verification" content="7s9wNJJCXTQqp6yr1GiQxREhloXKjtlbOIPTHZhtY04" />
124125
<meta name="yandex-verification" content="2eb7e8ef6fb55e24" />
125126

126-
{/* Preconnect to font origins for faster loading */}
127-
<link rel="preconnect" href="https://fonts.googleapis.com"/>
128-
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous"/>
129127

130128
{/* Preload CrazyGames SDK when on CrazyGames platform */}
131129
{inCrazyGames && (
132130
<link rel="preload" href="https://sdk.crazygames.com/crazygames-sdk-v3.js" as="script" />
133131
)}
134132

135-
{/* Fonts - blocking but with preconnect for speed */}
136-
{/* <link href="https://fonts.googleapis.com/css2?family=Jockey+One&display=swap" rel="stylesheet"/>
137-
<link href="https://fonts.googleapis.com/css2?family=Lexend:wght@100..900&display=swap" rel="stylesheet"/> */}
138-
139-
<link href="https://fonts.googleapis.com/css2?family=Jockey+One&family=Lexend:wght@100..900&display=swap" rel="stylesheet"/>
140-
141133
{/* <script disable-devtool-auto src='https://cdn.jsdelivr.net/npm/disable-devtool'></script> */}
142134

143135

components/home.js

Lines changed: 20 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -27,37 +27,29 @@ import 'react-toastify/dist/ReactToastify.css';
2727
import dynamic from "next/dynamic";
2828
import NextImage from "next/image";
2929
import OnboardingText from "@/components/onboardingText";
30-
// import RoundOverScreen from "@/components/roundOverScreen";
3130
const RoundOverScreen = dynamic(() => import('@/components/roundOverScreen'), { ssr: false });
3231
import msToTime from "@/components/msToTime";
3332
import SuggestAccountModal from "@/components/suggestAccountModal";
34-
import FriendsModal from "@/components/friendModal";
3533
import { toast, ToastContainer } from "react-toastify";
36-
import InfoModal from "@/components/infoModal";
3734
import { inIframe, isForbiddenIframe } from "@/components/utils/inIframe";
3835
import MapsModal from "@/components/maps/mapsModal";
39-
import { useRouter } from "next/router";
40-
import { fromLonLat } from "ol/proj";
41-
import { boundingExtent } from "ol/extent";
4236

4337
import countries from "@/public/countries.json";
4438
import officialCountryMaps from "@/public/officialCountryMaps.json";
4539

4640
import gameStorage from "@/components/utils/localStorage";
4741
import DiscordModal from "@/components/discordModal";
48-
import MerchModal from "@/components/merchModal";
4942
import AlertModal from "@/components/ui/AlertModal";
5043
import WhatsNewModal from "@/components/ui/WhatsNewModal";
51-
import MapGuessrModal from "@/components/mapGuessrModal";
44+
const MapGuessrModal = dynamic(() => import("@/components/mapGuessrModal"), { ssr: false });
5245
import changelog from "@/components/changelog.json";
5346
import clientConfig from "@/clientConfig";
5447
import { useGoogleLogin } from "@react-oauth/google";
55-
import haversineDistance from "./utils/haversineDistance";
48+
// import haversineDistance from "./utils/haversineDistance";
5649
import StreetView from "./streetview/streetView";
5750
import Stats from "stats.js";
5851
// import SvEmbedIframe from "./streetview/svHandler"; // REMOVED: Using direct StreetView instead of double-iframe setup
59-
import HomeNotice from "./homeNotice";
60-
import getTimeString, { getMaintenanceDate } from "./maintenanceTime";
52+
// import getTimeString, { getMaintenanceDate } from "./maintenanceTime";
6153
// import MaintenanceBanner from "./MaintenanceBanner";
6254
import Ad from "./bannerAdNitro";
6355
import PendingNameChangeModal from "./pendingNameChangeModal";
@@ -2153,11 +2145,10 @@ export default function Home({ }) {
21532145
setLatLong(loc)
21542146
if (data.name) {
21552147

2156-
// calculate extent (for openlayers)
2157-
const mappedLatLongs = data.locations.map((l) => fromLonLat([l.long, l.lat], 'EPSG:4326'));
2158-
let extent = boundingExtent(mappedLatLongs);
2159-
console.log("extent", extent)
2160-
// convert extent from EPSG:4326 to EPSG:3857 (for openlayers)
2148+
// calculate extent - simple bounding box [minLng, minLat, maxLng, maxLat]
2149+
const lngs = data.locations.map(l => l.long);
2150+
const lats = data.locations.map(l => l.lat);
2151+
const extent = [Math.min(...lngs), Math.min(...lats), Math.max(...lngs), Math.max(...lats)];
21612152

21622153
setGameOptions((prev) => ({
21632154
...prev,
@@ -2304,24 +2295,17 @@ export default function Home({ }) {
23042295
<>
23052296
<HeadContent text={text} inCoolMathGames={inCoolMathGames} inCrazyGames={inCrazyGames} />
23062297

2307-
<AccountModal inCrazyGames={inCrazyGames} shown={accountModalOpen} session={session} setSession={setSession} setAccountModalOpen={setAccountModalOpen}
2298+
{accountModalOpen && <AccountModal inCrazyGames={inCrazyGames} shown={true} session={session} setSession={setSession} setAccountModalOpen={setAccountModalOpen}
23082299
eloData={eloData} accountModalPage={accountModalPage} setAccountModalPage={setAccountModalPage}
23092300
ws={ws} canSendInvite={
2310-
// send invite if in a private multiplayer game, dont need to be host or in game waiting just need to be in a Party
23112301
multiplayerState?.inGame && !multiplayerState?.gameData?.public
23122302
} sendInvite={sendInvite} options={options}
2313-
2314-
/>
2315-
<SetUsernameModal shown={session && session?.token?.secret && !session.token.username} session={session} />
2316-
<SuggestAccountModal shown={showSuggestLoginModal} setOpen={setShowSuggestLoginModal} />
2317-
<DiscordModal shown={showDiscordModal && (typeof window !== 'undefined' && window.innerWidth >= 768)} setOpen={setShowDiscordModal} />
2318-
{/* <MerchModal shown={merchModal} onClose={() => setMerchModal(false)} session={session} /> */}
2319-
<MapGuessrModal isOpen={mapGuessrModal} onClose={() => setMapGuessrModal(false)} />
2320-
<PendingNameChangeModal
2321-
session={session}
2322-
isOpen={pendingNameChangeModal}
2323-
onClose={() => setPendingNameChangeModal(false)}
2324-
/>
2303+
/>}
2304+
{session?.token?.secret && !session.token.username && <SetUsernameModal shown={true} session={session} />}
2305+
{showSuggestLoginModal && <SuggestAccountModal shown={true} setOpen={setShowSuggestLoginModal} />}
2306+
{showDiscordModal && typeof window !== 'undefined' && window.innerWidth >= 768 && <DiscordModal shown={true} setOpen={setShowDiscordModal} />}
2307+
{mapGuessrModal && <MapGuessrModal isOpen={true} onClose={() => setMapGuessrModal(false)} />}
2308+
{pendingNameChangeModal && <PendingNameChangeModal session={session} isOpen={true} onClose={() => setPendingNameChangeModal(false)} />}
23252309
{ChatboxMemo}
23262310
<ToastContainer pauseOnFocusLoss={false} />
23272311

@@ -2799,8 +2783,7 @@ export default function Home({ }) {
27992783
</div>
28002784
</div>
28012785
}
2802-
<InfoModal shown={false} />
2803-
<MapsModal shown={mapModal || gameOptionsModalShown} session={session} onClose={() => {
2786+
{(mapModal || gameOptionsModalShown) && <MapsModal shown={true} session={session} onClose={() => {
28042787
if (mapModalClosing) return;
28052788
setMapModalClosing(true);
28062789
setTimeout(() => {
@@ -2816,12 +2799,12 @@ export default function Home({ }) {
28162799
} : null}
28172800
showAllCountriesOption={(gameOptionsModalShown && screen === "singleplayer")}
28182801
showOptions={screen === "singleplayer"}
2819-
gameOptions={gameOptions} setGameOptions={setGameOptions} />
2802+
gameOptions={gameOptions} setGameOptions={setGameOptions} />}
28202803

2821-
<SettingsModal inCrazyGames={inCrazyGames} options={options} setOptions={setOptions} shown={settingsModal} onClose={() => setSettingsModal(false)} />
2804+
{settingsModal && <SettingsModal inCrazyGames={inCrazyGames} options={options} setOptions={setOptions} shown={true} onClose={() => setSettingsModal(false)} />}
28222805

2823-
<AlertModal
2824-
isOpen={connectionErrorModalShown}
2806+
{connectionErrorModalShown && <AlertModal
2807+
isOpen={true}
28252808
onClose={() => setConnectionErrorModalShown(false)}
28262809
title={multiplayerState.connecting ? text("multiplayerConnecting") : text("multiplayerNotConnected")}
28272810
message={multiplayerState.connecting
@@ -2832,7 +2815,7 @@ export default function Home({ }) {
28322815
: text("multiplayerConnectionErrorMessage")
28332816
}
28342817
type={multiplayerState.connecting ? "warning" : "error"}
2835-
/>
2818+
/>}
28362819

28372820

28382821

@@ -2972,11 +2955,6 @@ document.addEventListener(
29722955
}
29732956
}, 1000);
29742957
2975-
(function(c,l,a,r,i,t,y){
2976-
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
2977-
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
2978-
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
2979-
})(window, document, "clarity", "script", "ndud94nvsg");
29802958
29812959
window.aiptag = window.aiptag || {cmd: []};
29822960
aiptag.cmd.display = aiptag.cmd.display || [];

components/ui/navbar.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ export default function Navbar({ maintenance, joinCodePress, inCrazyGames, inCoo
7979
{!inGame && showAccBtn && !inCoolMathGames && !accountModalOpen && !mapModalOpen && (<AccountBtn inCrazyGames={inCrazyGames} session={session} navbarMode={screen !== "home"} openAccountModal={openAccountModal} />)}
8080

8181
{session?.token?.secret && !accountModalOpen && !gameOptionsModalShown && !mapModalOpen && !["getready", "guess"].includes(multiplayerState?.gameData?.state) && (
82-
<button className={`gameBtn friendBtn ${screen === "home" ? "friendBtnFixed" : ""}`} onClick={onFriendsPress} disabled={!multiplayerState?.connected}>
82+
<button className={`gameBtn friendBtn ${screen === "home" ? "friendBtnFixed" : ""}`} onClick={onFriendsPress} disabled={!multiplayerState?.connected} aria-label="Friends">
8383
<FaUserFriends size={40} className={`friendBtnIcon ${screen === "home" ? "friendBtnIconFixed" : ""}`} />
8484
</button>
8585
)}

components/utils/sendEvent.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
export default function sendEvent(name, params={}) {
2-
const windowAny = window;
32
try {
4-
// gtag events
5-
windowAny.gtag("event",name,params)
3+
window.gtag("event", name, params);
64
} catch (e) {
7-
console.log("error sending gtag event",e)
5+
console.log("error sending gtag event", e);
86
}
97
}

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"private": true,
55
"type": "module",
66
"scripts": {
7+
"postinstall": "mkdir -p public/~partytown && cp -r node_modules/@qwik.dev/partytown/lib/* public/~partytown/ || true",
78
"dev": "node welcome.js && npx concurrently \"cross-env NODE_OPTIONS='--no-warnings' UWS_HTTP_MAX_HEADERS_SIZE=16384 nodemon ws/ws --quiet --signal SIGTERM --exec \"node --no-warnings=ExperimentalWarning\" --watch ws/ws.js\" \"cross-env NODE_OPTIONS='--no-warnings' nodemon server.js --quiet --exec \"node --no-warnings=ExperimentalWarning\" --watch server.js\" \"cross-env NODE_OPTIONS='--no-warnings' nodemon cron.js --quiet --exec \"node --no-warnings=ExperimentalWarning\" --watch cron.js\" \"npx next dev\"",
89
"devclient": "node welcome.js && npx concurrently \"cross-env NODE_OPTIONS='--no-warnings' nodemon server.js --quiet --exec \"node --no-warnings=ExperimentalWarning\" --watch server.js\" \"cross-env NODE_OPTIONS='--no-warnings' nodemon cron.js --quiet --exec \"node --no-warnings=ExperimentalWarning\" --watch cron.js\" \"npx next dev\"",
910
"build": "cross-env next build",
@@ -13,6 +14,7 @@
1314
"heroku-postbuild": "cross-env next build"
1415
},
1516
"dependencies": {
17+
"@qwik.dev/partytown": "^0.11.2",
1618
"@googlemaps/js-api-loader": "^1.16.6",
1719
"@osm_borders/maritime_10m": "^1.1.0",
1820
"@react-oauth/google": "^0.12.1",

pages/_app.js

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,39 @@ import "@/styles/accountModal.css";
44
import "@/styles/mapModal.css";
55
import '@/styles/duel.css';
66

7-
import { GoogleAnalytics } from "nextjs-google-analytics";
87
import { GoogleOAuthProvider } from '@react-oauth/google';
98

109
import { useEffect } from "react";
1110

1211
import '@smastrom/react-rating/style.css'
1312

1413
function App({ Component, pageProps }) {
14+
useEffect(() => {
15+
const handleError = (event) => {
16+
window.gtag?.('event', 'exception', {
17+
description: event.message || 'Unknown error',
18+
fatal: false,
19+
});
20+
};
21+
22+
const handleRejection = (event) => {
23+
window.gtag?.('event', 'exception', {
24+
description: event.reason?.message || 'Unhandled promise rejection',
25+
fatal: false,
26+
});
27+
};
28+
29+
window.addEventListener('error', handleError);
30+
window.addEventListener('unhandledrejection', handleRejection);
31+
32+
return () => {
33+
window.removeEventListener('error', handleError);
34+
window.removeEventListener('unhandledrejection', handleRejection);
35+
};
36+
}, []);
37+
1538
return (
1639
<>
17-
<GoogleAnalytics trackPageViews gaMeasurementId="G-KFK0S0RXG5" />
1840
{ process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID ? (
1941
<GoogleOAuthProvider clientId={process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID}>
2042
<Component {...pageProps} />

pages/_document.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,35 @@
11
import { Html, Head, Main, NextScript } from "next/document";
2-
import React, { useEffect } from "react";
2+
import { Partytown } from '@qwik.dev/partytown/react';
33

44
export default function Document() {
55
return (
66
<Html lang="en" style={{ backgroundColor: '#000000' }}>
77
<Head>
8+
{/* Font preconnects and stylesheets */}
9+
<link rel="preconnect" href="https://fonts.googleapis.com"/>
10+
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous"/>
11+
<link href="https://fonts.googleapis.com/css2?family=Jockey+One&family=Lexend:wght@100..900&family=Rubik:wght@400;500;600;700&display=swap" rel="stylesheet"/>
12+
13+
<Partytown
14+
debug={process.env.NODE_ENV === 'development'}
15+
forward={['nitroAds', 'nitroAds.createAd', 'nitroAds.queue', 'nitroAds.addUserToken', 'dataLayer.push', 'gtag']}
16+
/>
17+
{/* Google Analytics - gtag must be defined on main thread for forwarding to work */}
18+
<script
19+
dangerouslySetInnerHTML={{
20+
__html: `
21+
window.dataLayer = window.dataLayer || [];
22+
window.gtag = function(){dataLayer.push(arguments);}
23+
gtag('js', new Date());
24+
gtag('config', 'G-KFK0S0RXG5');
25+
`,
26+
}}
27+
/>
28+
{/* Load gtag.js in web worker via Partytown */}
29+
<script
30+
type="text/partytown"
31+
src="https://www.googletagmanager.com/gtag/js?id=G-KFK0S0RXG5"
32+
/>
833
<style dangerouslySetInnerHTML={{
934
__html: `
1035
html, body {

pages/leaderboard/index.js

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,7 @@ const Leaderboard = ({ }) => {
6565
<title>{text("leaderboard")}</title>
6666
<meta charSet="UTF-8" />
6767
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
68-
<link rel="preconnect" href="https://fonts.googleapis.com" />
69-
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="true" />
70-
<link
71-
href="https://fonts.googleapis.com/css2?family=Rubik:wght@400;500;600;700&display=swap"
72-
rel="stylesheet"
73-
/>
74-
<script src="https://unpkg.com/@phosphor/icons"></script>
68+
<script src="https://unpkg.com/@phosphor/icons"></script>
7569
<style>
7670
{`
7771
body {

0 commit comments

Comments
 (0)