Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions examples/react/with-responsive-image/.cta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"projectName": "with-responsive-image",
"mode": "file-router",
"typescript": true,
"tailwind": false,
"packageManager": "npm",
"git": true,
"version": 1,
"framework": "react-cra",
"chosenAddOns": []
}
9 changes: 9 additions & 0 deletions examples/react/with-responsive-image/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
node_modules
.DS_Store
dist
dist-ssr
*.local
count.txt
.env
.nitro
.tanstack
5 changes: 5 additions & 0 deletions examples/react/with-responsive-image/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# TanStack + ResponsiveImage

Integrating [ResponsiveImage](https://responsive-image.dev) with TanStack Router.

Run `pnpm dev` to run locally.
20 changes: 20 additions & 0 deletions examples/react/with-responsive-image/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="/favicon.ico" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-tsrouter-app"
/>
<link rel="apple-touch-icon" href="/logo192.png" />
<link rel="manifest" href="/manifest.json" />
<title>Create TanStack App - with-responsive-image</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
33 changes: 33 additions & 0 deletions examples/react/with-responsive-image/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "with-responsive-image",
"private": true,
"type": "module",
"scripts": {
"dev": "vite --port 3000",
"start": "vite --port 3000",
"build": "vite build && tsc",
"serve": "vite preview"
},
"dependencies": {
"@responsive-image/core": "^2.1.0",
"@responsive-image/react": "^1.1.2",
"@tanstack/react-devtools": "^0.2.2",
"@tanstack/react-router": "^1.131.32",
"@tanstack/react-router-devtools": "^1.131.32",
"@tanstack/router-plugin": "^1.131.32",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@responsive-image/vite-plugin": "^2.0.0",
"@testing-library/dom": "^10.4.0",
"@testing-library/react": "^16.2.0",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"@vitejs/plugin-react": "^4.3.4",
"jsdom": "^26.0.0",
"typescript": "^5.7.2",
"vite": "^7.1.3",
"web-vitals": "^4.2.4"
}
}
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 25 additions & 0 deletions examples/react/with-responsive-image/public/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"short_name": "TanStack App",
"name": "Create TanStack App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
3 changes: 3 additions & 0 deletions examples/react/with-responsive-image/public/robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
6 changes: 6 additions & 0 deletions examples/react/with-responsive-image/src/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
declare module '*responsive' {
import type { ImageData } from '@responsive-image/core'

const value: ImageData
export default value
}
51 changes: 51 additions & 0 deletions examples/react/with-responsive-image/src/images.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { notFound } from '@tanstack/react-router'
import type { ImageData } from '@responsive-image/core'

const thumbnails = import.meta.glob<{ default: ImageData }>(
'./images/gallery/*.jpg',
{
eager: true, // this is just generating image meta data, not need for lazy loading
query: {
w: '200;400',
responsive: true, // opt into processing by @responsive-image/vite, see vite.config.ts. Without this, default vite asset handling applies.
},
},
)

const images = import.meta.glob<{ default: ImageData }>(
'./images/gallery/*.jpg',
{
eager: true, // this is just generating image meta data, not need for lazy loading
query: {
responsive: true, // opt into processing by @responsive-image/vite, see vite.config.ts. Without this, default vite asset handling applies.
},
},
)

export function getThumbsnails(): Record<string, ImageData> {
return Object.fromEntries(
Object.entries(thumbnails).map(([imageId, module]) => [
normalizeImageId(imageId),
module.default,
]),
)
}

export function getImage(imageId: string): ImageData {
const module = images[denormalizeImageId(imageId)]

if (!module) {
throw notFound({ data: { foo: 1 } })
}

return module.default
}

// Remove leading `./images/gallery/` from import.meta.glob keys for nicer URLs
function normalizeImageId(imageId: string): string {
return imageId.replace(/^\.\/images\/gallery\//, '')
}

function denormalizeImageId(imageId: string): string {
return `./images/gallery/${imageId}`
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 42 additions & 0 deletions examples/react/with-responsive-image/src/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { StrictMode } from 'react'
import ReactDOM from 'react-dom/client'
import { RouterProvider, createRouter } from '@tanstack/react-router'

// Import the generated route tree
import { routeTree } from './routeTree.gen'

import './styles.css'
import reportWebVitals from './reportWebVitals.ts'

// Create a new router instance
const router = createRouter({
routeTree,
context: {},
defaultPreload: 'intent',
scrollRestoration: true,
defaultStructuralSharing: true,
defaultPreloadStaleTime: 0,
})

// Register the router instance for type safety
declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}

// Render the app
const rootElement = document.getElementById('app')
if (rootElement && !rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement)
root.render(
<StrictMode>
<RouterProvider router={router} />
</StrictMode>,
)
}

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals()
13 changes: 13 additions & 0 deletions examples/react/with-responsive-image/src/reportWebVitals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const reportWebVitals = (onPerfEntry?: () => void) => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ onCLS, onINP, onFCP, onLCP, onTTFB }) => {
onCLS(onPerfEntry)
onINP(onPerfEntry)
onFCP(onPerfEntry)
onLCP(onPerfEntry)
onTTFB(onPerfEntry)
})
}
}

export default reportWebVitals
77 changes: 77 additions & 0 deletions examples/react/with-responsive-image/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/* eslint-disable */

// @ts-nocheck

// noinspection JSUnusedGlobalSymbols

// This file was automatically generated by TanStack Router.
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.

import { Route as rootRouteImport } from './routes/__root'
import { Route as ImageIdRouteImport } from './routes/$imageId'
import { Route as IndexRouteImport } from './routes/index'

const ImageIdRoute = ImageIdRouteImport.update({
id: '/$imageId',
path: '/$imageId',
getParentRoute: () => rootRouteImport,
} as any)
const IndexRoute = IndexRouteImport.update({
id: '/',
path: '/',
getParentRoute: () => rootRouteImport,
} as any)

export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/$imageId': typeof ImageIdRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/$imageId': typeof ImageIdRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
'/$imageId': typeof ImageIdRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/' | '/$imageId'
fileRoutesByTo: FileRoutesByTo
to: '/' | '/$imageId'
id: '__root__' | '/' | '/$imageId'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
ImageIdRoute: typeof ImageIdRoute
}

declare module '@tanstack/react-router' {
interface FileRoutesByPath {
'/$imageId': {
id: '/$imageId'
path: '/$imageId'
fullPath: '/$imageId'
preLoaderRoute: typeof ImageIdRouteImport
parentRoute: typeof rootRouteImport
}
'/': {
id: '/'
path: '/'
fullPath: '/'
preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport
}
}
}

const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
ImageIdRoute: ImageIdRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>()
14 changes: 14 additions & 0 deletions examples/react/with-responsive-image/src/routes/$imageId.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { createFileRoute } from '@tanstack/react-router'
import { ResponsiveImage } from '@responsive-image/react'
import { getImage } from '../images.ts'

export const Route = createFileRoute('/$imageId')({
loader: ({ params }) => getImage(params.imageId),
component: Image,
})

function Image() {
const image = Route.useLoaderData()

return <ResponsiveImage src={image} className="large" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add accessible alt text (and consider a remount key to work around the ThumbHash LQIP bug).

Provide meaningful alt text; optionally key by imageId to force a remount when switching images.

Apply:

 function Image() {
-  const image = Route.useLoaderData()
+  const image = Route.useLoaderData()
+  const { imageId } = Route.useParams()

-  return <ResponsiveImage src={image} className="large" />
+  return (
+    <ResponsiveImage
+      key={imageId}
+      src={image}
+      className="large"
+      alt={image.alt ?? image.title ?? imageId}
+    />
+  )
 }

Also applies to: 12-12

🤖 Prompt for AI Agents
In examples/react/with-responsive-image/src/routes/$imageId.tsx around lines 13
(and also line 12), the ResponsiveImage is rendered without accessible alt text
and without a remount key to avoid the ThumbHash LQIP bug; update the component
props to include a meaningful alt (e.g., derived from imageId or image metadata)
and add key={imageId} so the component remounts when switching images, ensuring
accessibility and working around the LQIP bug.

}
38 changes: 38 additions & 0 deletions examples/react/with-responsive-image/src/routes/__root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Outlet, Link, createRootRoute } from '@tanstack/react-router'
import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools'
import { TanstackDevtools } from '@tanstack/react-devtools'
import { ResponsiveImage } from '@responsive-image/react'
import './app.css'
import { getThumbsnails } from '../images.ts'

export const Route = createRootRoute({
component: App,
})

function App() {
return (
<>
<aside>
{Object.entries(getThumbsnails()).map(([imageId, image]) => (
<Link to="/$imageId" params={{ imageId }} key={imageId}>
<ResponsiveImage src={image} width={200}></ResponsiveImage>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add alt text to thumbnails and use a self-closing tag.

Improves accessibility and consistency.

Apply:

-            <ResponsiveImage src={image} width={200}></ResponsiveImage>
+            <ResponsiveImage
+              src={image}
+              width={200}
+              alt={image.alt || image.title || imageId}
+            />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<ResponsiveImage src={image} width={200}></ResponsiveImage>
<ResponsiveImage
src={image}
width={200}
alt={image.alt || image.title || imageId}
/>
🤖 Prompt for AI Agents
In examples/react/with-responsive-image/src/routes/__root.tsx around line 18,
the ResponsiveImage usage lacks an alt attribute and uses an explicit closing
tag; update the component to include a descriptive alt prop (e.g.,
alt="thumbnail" or a value derived from the image variable) and convert it to a
self-closing tag (<ResponsiveImage ... />) to improve accessibility and
consistency.

</Link>
))}
</aside>
<main>
<Outlet />
</main>
<TanstackDevtools
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice using the new devtools!

config={{
position: 'bottom-left',
}}
plugins={[
{
name: 'Tanstack Router',
render: <TanStackRouterDevtoolsPanel />,
},
]}
/>
</>
)
}
34 changes: 34 additions & 0 deletions examples/react/with-responsive-image/src/routes/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
aside {
display: flex;
overflow: auto;
justify-content: center;

margin-bottom: 5px;
}

aside a {
border: solid 1px transparent;
}

aside a.active {
border-color: #00c2ab;
}

img {
display: block;
}

main {
display: flex;
justify-content: center;
flex-wrap: wrap;
}

.intro {
padding: 20px;
}

img.large {
max-height: calc(100vh - 140px);
width: auto;
}
Loading