Skip to content

Commit 3fe05ab

Browse files
authored
Client hints (#30)
Fixes # # Description Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. ## Type of change Please mark relevant options with an `x` in the brackets. - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] This change requires a documentation update - [ ] Algorithm update - updates algorithm documentation/questions/answers etc. - [ ] Other (please describe): # How Has This Been Tested? Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration - [ ] Integration tests - [ ] Unit tests - [ ] Manual tests - [ ] No tests required # Reviewer checklist Mark everything that needs to be checked before merging the PR. - [ ] Check if the UI is working as expected and is satisfactory - [ ] Check if the code is well documented - [ ] Check if the behavior is what is expected - [ ] Check if the code is well tested - [ ] Check if the code is readable and well formatted - [ ] Additional checks (document below if any) # Screenshots (if appropriate): # Questions (if appropriate):
1 parent f10878a commit 3fe05ab

File tree

5 files changed

+67
-12
lines changed

5 files changed

+67
-12
lines changed

app/root.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ import type { LinksFunction } from "react-router"
44
import { useChangeLanguage } from "remix-i18next/react"
55
import type { Route } from "./+types/root"
66
import { LanguageSwitcher } from "./library/language-switcher"
7+
import { ClientHintCheck, getHints } from "./services/client-hints"
78
import tailwindcss from "./tailwind.css?url"
89

9-
export async function loader({ context }: Route.LoaderArgs) {
10+
export async function loader({ context, request }: Route.LoaderArgs) {
1011
const { lang, clientEnv } = context
11-
return { lang, clientEnv }
12+
const hints = getHints(request)
13+
return { lang, clientEnv, hints }
1214
}
1315

1416
export const links: LinksFunction = () => [{ rel: "stylesheet", href: tailwindcss }]
@@ -20,7 +22,6 @@ export const handle = {
2022
export default function App({ loaderData }: Route.ComponentProps) {
2123
const { lang, clientEnv } = loaderData
2224
useChangeLanguage(lang)
23-
2425
return (
2526
<>
2627
<Outlet />
@@ -35,6 +36,7 @@ export const Layout = ({ children }: { children: React.ReactNode }) => {
3536
return (
3637
<html className="overflow-y-auto overflow-x-hidden" lang={i18n.language} dir={i18n.dir()}>
3738
<head>
39+
<ClientHintCheck />
3840
<meta charSet="utf-8" />
3941
<meta name="viewport" content="width=device-width, initial-scale=1" />
4042
<Meta />

app/services/client-hints.tsx

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { getHintUtils } from "@epic-web/client-hints"
2+
import { clientHint as colorSchemeHint, subscribeToSchemeChange } from "@epic-web/client-hints/color-scheme"
3+
import { clientHint as reducedMotionHint, subscribeToMotionChange } from "@epic-web/client-hints/reduced-motion"
4+
import { clientHint as timeZoneHint } from "@epic-web/client-hints/time-zone"
5+
import { useEffect } from "react"
6+
import { useRevalidator, useRouteLoaderData } from "react-router"
7+
import type { Route } from "../+types/root"
8+
9+
export const { getHints, getClientHintCheckScript } = getHintUtils({
10+
theme: colorSchemeHint,
11+
timeZone: timeZoneHint,
12+
reducedMotion: reducedMotionHint,
13+
// add other hints here
14+
})
15+
16+
/**
17+
* @public
18+
* Utility function used to get the time zone for the current users browser on either the client or the server.
19+
* */
20+
export const getTimeZone = (request?: Request) => getHints(request).timeZone
21+
22+
/**
23+
* @public
24+
* Utility used to get the client hints for the current users browser.
25+
* */
26+
export function useHints() {
27+
const requestInfo = useRouteLoaderData<Route.ComponentProps["loaderData"]>("root")
28+
return requestInfo?.hints
29+
}
30+
/**
31+
* Utility component used to check the client hints on the client and send them to the server.
32+
*/
33+
export function ClientHintCheck({ nonce }: { nonce?: string }) {
34+
const { revalidate } = useRevalidator()
35+
useEffect(() => subscribeToSchemeChange(() => revalidate()), [revalidate])
36+
useEffect(() => subscribeToMotionChange(() => revalidate()), [revalidate])
37+
38+
return (
39+
<script
40+
nonce={nonce}
41+
// biome-ignore lint/security/noDangerouslySetInnerHtml: We want to run this script on the client
42+
dangerouslySetInnerHTML={{
43+
__html: getClientHintCheckScript(),
44+
}}
45+
/>
46+
)
47+
}

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"postinstall": "pnpm run typegen"
2828
},
2929
"dependencies": {
30+
"@epic-web/client-hints": "1.3.5",
3031
"@forge42/seo-tools": "1.3.0",
3132
"@react-router/node": "7.1.5",
3233
"clsx": "2.1.1",
@@ -74,7 +75,7 @@
7475
"typescript": "5.7.3",
7576
"vite": "6.0.11",
7677
"vite-plugin-babel": "1.3.0",
77-
"vite-plugin-icons-spritesheet": "3.0.0",
78+
"vite-plugin-icons-spritesheet": "3.0.1",
7879
"vite-tsconfig-paths": "5.1.4",
7980
"vitest": "3.0.5",
8081
"vitest-browser-react": "0.0.4"

pnpm-lock.yaml

Lines changed: 13 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vite.config.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,6 @@ export default defineConfig({
3737
formatter: "biome",
3838
}),
3939
],
40-
build: {
41-
assetsInlineLimit: 0,
42-
},
4340
server: {
4441
open: true,
4542
// biome-ignore lint/nursery/noProcessEnv: Its ok to use process.env here

0 commit comments

Comments
 (0)