-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Feat/remix example #6703
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Feat/remix example #6703
Conversation
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
|
@shogo7 is attempting to deploy a commit to the trpc Team on Vercel. A member of the Team first needs to authorize it. |
WalkthroughThis pull request introduces a comprehensive setup for a Remix-based tRPC project. New configuration and documentation files have been added at the project root, while distinct client and server directories now contain their respective entry points, ESLint, Vite, Tailwind CSS, and TypeScript configuration files. The client side integrates TRPC with React Query through dedicated entry and route components, and the server side sets up an Express server with a TRPC router providing fruit-related endpoints. Additionally, workspace configuration files are included to manage multiple packages. Changes
Sequence Diagram(s)sequenceDiagram
participant Browser
participant RemixApp
participant TRPCClient
participant ExpressServer
participant TRPCRouter
participant FruitModel
Browser->>RemixApp: Request page load
RemixApp->>TRPCClient: Initiate API call (getFruits/getFruitById)
TRPCClient->>ExpressServer: HTTP request to /trpc endpoint
ExpressServer->>TRPCRouter: Forward request to TRPC router
TRPCRouter->>FruitModel: Query fruit data
FruitModel-->>TRPCRouter: Return fruit data
TRPCRouter-->>ExpressServer: Send API response
ExpressServer-->>TRPCClient: Return API response
TRPCClient-->>RemixApp: Provide fruit data
RemixApp-->>Browser: Render page with fruit data
Poem
✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
@trpc/client
@trpc/next
@trpc/server
@trpc/react-query
@trpc/tanstack-react-query
@trpc/upgrade
commit: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🧹 Nitpick comments (21)
examples/remix-trpc/server/src/index.ts (1)
1-26: Express server setup looks good, consider adding error handlingThe tRPC server setup with Express is implemented correctly. The CORS configuration is appropriate for an API server, and the middleware is properly configured.
Consider adding error handling for robustness:
app.listen(PORT, () => { console.log(`🚀 tRPC API running at http://localhost:${PORT}/trpc`); +}).on('error', (err) => { + console.error('Failed to start server:', err); });examples/remix-trpc/.gitignore (2)
5-10: Remove duplicate entriesThere are duplicate entries for TypeScript build info files.
*.log *.tsbuildinfo # TypeScript -*.tsbuildinfo +# Additional TypeScript files
24-25: Remove duplicate entryLog files are already ignored on line 5.
# Log files -*.logexamples/remix-trpc/server/src/routes/fruits.ts (1)
7-9: Consider adding pagination and filtering optionsThis endpoint returns all fruits without any pagination or filtering. For real-world applications with potentially large datasets, consider implementing pagination and filtering options to improve performance and user experience.
-router.get('/', (req, res) => { - res.json(fruits); +router.get('/', (req: Request, res: Response) => { + const { limit = 10, page = 1, color } = req.query; + let filteredFruits = [...fruits]; + + if (color) { + filteredFruits = filteredFruits.filter(fruit => + fruit.color.toLowerCase() === String(color).toLowerCase()); + } + + const startIndex = (Number(page) - 1) * Number(limit); + const paginatedFruits = filteredFruits.slice(startIndex, startIndex + Number(limit)); + + res.json({ + total: filteredFruits.length, + page: Number(page), + limit: Number(limit), + data: paginatedFruits + }); });examples/remix-trpc/client/app/entry.client.tsx (1)
20-31: Consider adding error handling for client-side hydrationYour hydration setup looks good, but there's no error handling for potential issues during hydration or when the tRPC server is unavailable. Consider adding error boundaries or fallback UI components.
startTransition(() => { + try { hydrateRoot( document, <StrictMode> + <ErrorBoundary fallback={<div>Something went wrong</div>}> <trpc.Provider client={trpcClient} queryClient={queryClient}> <QueryClientProvider client={queryClient}> <RemixBrowser /> </QueryClientProvider> </trpc.Provider> + </ErrorBoundary> </StrictMode>, ); + } catch (error) { + console.error('Error during hydration:', error); + // Fallback rendering if needed + } });You'll need to implement
ErrorBoundaryor import it from a library likereact-error-boundary.examples/remix-trpc/server/tsconfig.json (1)
2-9: Consider adding additional helpful compiler optionsWhile your current configuration is solid, you might benefit from these additional compiler options:
{ "compilerOptions": { "module": "NodeNext", "moduleResolution": "NodeNext", "target": "ES2020", "esModuleInterop": true, "strict": true, "strictNullChecks": true, + "sourceMap": true, + "outDir": "dist", + "declaration": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true }, "include": ["src/**/*"] }These options will:
- Generate source maps for debugging
- Specify an output directory
- Generate declaration files
- Enable JSON imports
- Skip type checking of declaration files
- Enforce consistent file naming
examples/remix-trpc/server/src/trpc/index.ts (1)
1-5: Consider adding input validation for getFruits procedureWhile the current implementation works, consider adding input validation for filtering and pagination to make the API more flexible:
-import { initTRPC } from '@trpc/server'; +import { initTRPC, TRPCError } from '@trpc/server'; import superjson from 'superjson'; import { z } from 'zod'; import { fruits } from '../models/fruit.js';Then update the getFruits procedure:
-getFruits: t.procedure.query(() => { - return fruits; +getFruits: t.procedure + .input(z.object({ + limit: z.number().min(1).max(100).optional().default(10), + page: z.number().min(1).optional().default(1), + color: z.string().optional() + }).optional().default({})) + .query(({ input }) => { + let filteredFruits = [...fruits]; + + if (input.color) { + filteredFruits = filteredFruits.filter(fruit => + fruit.color.toLowerCase() === input.color.toLowerCase()); + } + + const startIndex = (input.page - 1) * input.limit; + const paginatedFruits = filteredFruits.slice(startIndex, startIndex + input.limit); + + return { + total: filteredFruits.length, + page: input.page, + limit: input.limit, + data: paginatedFruits + }; }),This makes the tRPC API more powerful and consistent with REST API best practices.
examples/remix-trpc/client/app/routes/_index.tsx (1)
20-21: Consider improving color contrast for accessibilityThe current approach of setting text color to match the fruit's color might lead to poor contrast against the white background, especially for colors like yellow.
- <p> - Color: <span style={{ color: fruit.color }}>{fruit.color}</span> - </p> + <p> + Color: <span className="px-2 py-1 rounded" style={{ + backgroundColor: fruit.color, + color: getContrastColor(fruit.color) + }}>{fruit.color}</span> + </p>Consider adding a helper function to determine a contrasting text color:
function getContrastColor(bgColor: string): string { // For simplicity, you can use predefined mappings // or implement a luminance calculation algorithm const colors: Record<string, string> = { 'Yellow': '#000', 'Red': '#fff', 'Purple': '#fff', 'Orange': '#000', // Add more as needed }; return colors[bgColor] || '#000'; }examples/remix-trpc/client/app/routes/fruits.$id.tsx (2)
20-26: Enhance error handlingWhile the basic error handling is in place, it could be improved to provide more specific error messages based on the type of error encountered.
try { const fruit = await helpers.getFruitById.fetch(id); return { fruit }; } catch (error) { console.error(`Error loading fruit ${id}:`, error); - throw new Response('Fruit not found', { status: 404 }); + // Check for specific error types + if (error instanceof Error && error.message.includes('not found')) { + throw new Response(`Fruit with ID ${id} not found`, { status: 404 }); + } + // Handle unexpected errors + throw new Response('Failed to load fruit data', { + status: 500, + statusText: error instanceof Error ? error.message : 'Unknown error' + }); }
40-41: Consider improving color contrast for accessibilitySimilar to the index page, the direct use of the fruit's color for text might create accessibility issues with certain colors like yellow on white background.
Consider implementing the same solution as suggested for the index page to ensure proper contrast.
examples/remix-trpc/client/.eslintrc.cjs (1)
59-67: Confirm import resolver configuration matches project conventionsThe internal regex pattern for imports starting with '~/' is configured here. Ensure this matches the actual import conventions used in your project for proper path resolution.
examples/remix-trpc/client/app/root.tsx (3)
30-30: Update HTML language attributeThe HTML language attribute is set to "ja" (Japanese). For an example project, you might want to change this to "en" unless Japanese is the intended language for this example.
- <html lang="ja"> + <html lang="en">
51-53: Use environment variables for API endpointThe tRPC client URL is hard-coded to 'http://localhost:3010/trpc', which wouldn't work in a production environment. Consider using environment variables to make it configurable across different environments.
httpBatchLink({ - url: 'http://localhost:3010/trpc', + url: process.env.TRPC_API_URL || 'http://localhost:3010/trpc', transformer: superjson, }),
58-64: Add error handling for API requestsThere's no error handling strategy for the tRPC client or Query client. Consider adding error boundaries to handle API failures gracefully and improve user experience.
examples/remix-trpc/server/package.json (2)
17-18: Remove client-side dependencies if not usedThe server package includes
@trpc/clientand@trpc/react-query, which are typically client-side dependencies. If these aren't used in the server code, consider removing them to reduce dependency bloat.
9-9: Update test script to avoid CI/CD failuresThe test script exits with an error code, which could cause CI/CD pipelines to fail. Consider updating it to:
- "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"No tests specified\""examples/remix-trpc/README.md (4)
17-34: Add language specification to code blockThe code block for project structure doesn't specify a language, which is flagged by markdownlint. Add a language specifier, even something generic like 'text' or 'plaintext'.
-``` +```text remix-trpc/ ├── client/ # Remix application ├── server/ # Express + tRPC server └── ...🧰 Tools
🪛 markdownlint-cli2 (0.17.2)
17-17: Fenced code blocks should have a language specified
null(MD040, fenced-code-language)
47-47: Clarify port configurationThe README mentions accessing the application at
http://localhost:5173/, but the client code sets up the tRPC client to connect tohttp://localhost:3010/trpc. Make sure to clarify this port configuration to avoid confusion.
38-42: Add pnpm prerequisite to getting startedThe getting started guide assumes that
pnpmis already installed. Consider adding instructions for installingpnpmas a prerequisite.1. Clone the repository -2. Install dependencies: +2. Install pnpm if you haven't already: + ```bash + npm install -g pnpm + ``` +3. Install dependencies: ```bash pnpm install ```
59-59: Address language tool suggestionsThere are a few minor grammar and style issues:
- Line 59: Consider adding "the" - "Client-side type error when using the wrong type:"
- Line 81: The phrase should be "When you need to use React Query's..."
- Line 121: Consider rephrasing "Feel free to submit issues..." to something more professional
Also applies to: 81-81, 121-121
🧰 Tools
🪛 LanguageTool
[uncategorized] ~59-~59: You might be missing the article “the” here.
Context: ...RPC) Client-side type error when using wrong type: 
examples/remix-trpc/client/app/entry.server.tsx (1)
41-139: Consider reducing code duplication between request handlersThere's significant duplication between
handleBotRequestandhandleBrowserRequest. While the different event handlers (onAllReadyvsonShellReady) necessitate separate functions, you could extract the common logic into a shared utility function.+function createRequestHandler( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext, + onReadyEvent: 'onAllReady' | 'onShellReady' +) { + return new Promise((resolve, reject) => { + let shellRendered = false; + const { pipe, abort } = renderToPipeableStream( + <RemixServer + context={remixContext} + url={request.url} + abortDelay={ABORT_DELAY} + />, + { + [onReadyEvent]() { + shellRendered = true; + const body = new PassThrough(); + const stream = createReadableStreamFromReadable(body); + + responseHeaders.set('Content-Type', 'text/html'); + + resolve( + new Response(stream, { + headers: responseHeaders, + status: responseStatusCode, + }), + ); + + pipe(body); + }, + onShellError(error: unknown) { + reject(error); + }, + onError(error: unknown) { + responseStatusCode = 500; + if (shellRendered) { + console.error(error); + } + }, + }, + ); + + setTimeout(abort, ABORT_DELAY); + }); +} + function handleBotRequest( request: Request, responseStatusCode: number, responseHeaders: Headers, remixContext: EntryContext, ) { - return new Promise((resolve, reject) => { - let shellRendered = false; - const { pipe, abort } = renderToPipeableStream( - <RemixServer - context={remixContext} - url={request.url} - abortDelay={ABORT_DELAY} - />, - { - onAllReady() { - shellRendered = true; - const body = new PassThrough(); - const stream = createReadableStreamFromReadable(body); - - responseHeaders.set('Content-Type', 'text/html'); - - resolve( - new Response(stream, { - headers: responseHeaders, - status: responseStatusCode, - }), - ); - - pipe(body); - }, - onShellError(error: unknown) { - reject(error); - }, - onError(error: unknown) { - responseStatusCode = 500; - // Log streaming rendering errors from inside the shell. Don't log - // errors encountered during initial shell rendering since they'll - // reject and get logged in handleDocumentRequest. - if (shellRendered) { - console.error(error); - } - }, - }, - ); - - setTimeout(abort, ABORT_DELAY); - }); + return createRequestHandler( + request, + responseStatusCode, + responseHeaders, + remixContext, + 'onAllReady' + ); } function handleBrowserRequest( request: Request, responseStatusCode: number, responseHeaders: Headers, remixContext: EntryContext, ) { - return new Promise((resolve, reject) => { - let shellRendered = false; - const { pipe, abort } = renderToPipeableStream( - <RemixServer - context={remixContext} - url={request.url} - abortDelay={ABORT_DELAY} - />, - { - onShellReady() { - shellRendered = true; - const body = new PassThrough(); - const stream = createReadableStreamFromReadable(body); - - responseHeaders.set('Content-Type', 'text/html'); - - resolve( - new Response(stream, { - headers: responseHeaders, - status: responseStatusCode, - }), - ); - - pipe(body); - }, - onShellError(error: unknown) { - reject(error); - }, - onError(error: unknown) { - responseStatusCode = 500; - // Log streaming rendering errors from inside the shell. Don't log - // errors encountered during initial shell rendering since they'll - // reject and get logged in handleDocumentRequest. - if (shellRendered) { - console.error(error); - } - }, - }, - ); - - setTimeout(abort, ABORT_DELAY); - }); + return createRequestHandler( + request, + responseStatusCode, + responseHeaders, + remixContext, + 'onShellReady' + ); }🧰 Tools
🪛 ESLint
[error] 41-41: Function 'handleBotRequest' has too many parameters (4). Maximum allowed is 3.
(max-params)
[error] 91-91: Function 'handleBrowserRequest' has too many parameters (4). Maximum allowed is 3.
(max-params)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (9)
examples/remix-trpc/assets/code1.pngis excluded by!**/*.pngexamples/remix-trpc/assets/code2.pngis excluded by!**/*.pngexamples/remix-trpc/assets/demo1.pngis excluded by!**/*.pngexamples/remix-trpc/assets/demo2.pngis excluded by!**/*.pngexamples/remix-trpc/assets/demo3.pngis excluded by!**/*.pngexamples/remix-trpc/client/public/favicon.icois excluded by!**/*.icoexamples/remix-trpc/client/public/logo-dark.pngis excluded by!**/*.pngexamples/remix-trpc/client/public/logo-light.pngis excluded by!**/*.pngpnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (23)
examples/remix-trpc/.gitignore(1 hunks)examples/remix-trpc/README.md(1 hunks)examples/remix-trpc/client/.eslintrc.cjs(1 hunks)examples/remix-trpc/client/app/entry.client.tsx(1 hunks)examples/remix-trpc/client/app/entry.server.tsx(1 hunks)examples/remix-trpc/client/app/lib/trpc.ts(1 hunks)examples/remix-trpc/client/app/root.tsx(1 hunks)examples/remix-trpc/client/app/routes/_index.tsx(1 hunks)examples/remix-trpc/client/app/routes/fruits.$id.tsx(1 hunks)examples/remix-trpc/client/app/tailwind.css(1 hunks)examples/remix-trpc/client/package.json(1 hunks)examples/remix-trpc/client/postcss.config.js(1 hunks)examples/remix-trpc/client/tailwind.config.ts(1 hunks)examples/remix-trpc/client/tsconfig.json(1 hunks)examples/remix-trpc/client/vite.config.ts(1 hunks)examples/remix-trpc/package.json(1 hunks)examples/remix-trpc/pnpm-workspace.yaml(1 hunks)examples/remix-trpc/server/package.json(1 hunks)examples/remix-trpc/server/src/index.ts(1 hunks)examples/remix-trpc/server/src/models/fruit.ts(1 hunks)examples/remix-trpc/server/src/routes/fruits.ts(1 hunks)examples/remix-trpc/server/src/trpc/index.ts(1 hunks)examples/remix-trpc/server/tsconfig.json(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (4)
examples/remix-trpc/client/app/routes/_index.tsx (2)
examples/remix-trpc/server/src/models/fruit.ts (1)
fruits(9-15)examples/remix-trpc/client/app/lib/trpc.ts (1)
trpc(5-5)
examples/remix-trpc/client/app/lib/trpc.ts (1)
examples/remix-trpc/server/src/trpc/index.ts (1)
AppRouter(21-21)
examples/remix-trpc/client/app/root.tsx (3)
examples/minimal-react/client/src/utils/trpc.ts (1)
queryClient(6-12)examples/remix-trpc/client/app/lib/trpc.ts (1)
trpc(5-5)packages/client/src/links/httpBatchLink.ts (1)
httpBatchLink(21-141)
examples/remix-trpc/server/src/trpc/index.ts (1)
examples/remix-trpc/server/src/models/fruit.ts (1)
fruits(9-15)
🪛 LanguageTool
examples/remix-trpc/README.md
[uncategorized] ~59-~59: You might be missing the article “the” here.
Context: ...RPC) Client-side type error when using wrong type: 
[grammar] ~81-~81: The verb “React” needs to be in the to-infinitive form.
Context: ...Pages that update frequently - When you need React Query's powerful features like caching ...
(MISSING_TO_BEFORE_A_VERB)
[style] ~121-~121: Consider using a less frequent alternative to set your writing apart from others and make it sound more professional.
Context: ...hecking for API calls ## Contributing Feel free to submit issues or pull requests to impro...
(FEEL_FREE_TO_STYLE_ME)
🪛 markdownlint-cli2 (0.17.2)
examples/remix-trpc/README.md
17-17: Fenced code blocks should have a language specified
null
(MD040, fenced-code-language)
🪛 ESLint
examples/remix-trpc/client/app/entry.server.tsx
[error] 16-16: Function 'handleRequest' has too many parameters (5). Maximum allowed is 3.
(max-params)
[error] 41-41: Function 'handleBotRequest' has too many parameters (4). Maximum allowed is 3.
(max-params)
[error] 91-91: Function 'handleBrowserRequest' has too many parameters (4). Maximum allowed is 3.
(max-params)
🔇 Additional comments (30)
examples/remix-trpc/client/app/tailwind.css (2)
1-3: Proper Tailwind Directives Initialization
The inclusion of the@tailwind base;,@tailwind components;, and@tailwind utilities;directives is correctly implemented and sets up the file for using Tailwind CSS effectively.
5-12: Consistent Styling with Dark Mode Support
Thehtml, bodyblock applies a white background in light mode and a dark background (dark:bg-gray-950) when dark mode is active. The nested media query further enforces the dark color-scheme based on system preferences. This approach is clear and aligns well with best practices for responsive design.examples/remix-trpc/client/postcss.config.js (1)
1-6: Valid PostCSS Configuration
The configuration correctly exports an object with apluginsproperty that includestailwindcssandautoprefixer(both initialized with empty options). This setup is standard for processing Tailwind styles and ensuring vendor prefixes are added automatically.examples/remix-trpc/client/app/lib/trpc.ts (1)
1-6: Well-Integrated tRPC Setup
The file importscreateTRPCReactfrom@trpc/react-queryand the server’sAppRoutertype correctly. Creating thetrpcconstant withcreateTRPCReact<AppRouter>()ensures end-to-end type safety between the client and server. The relative import path appears accurate given the monorepo structure.examples/remix-trpc/server/src/models/fruit.ts (2)
1-6: Effective Interface Definition for Fruit Data
TheFruitinterface is clear and concise, specifying all necessary properties (id,name,color, andprice) for a fruit record. This provides a solid foundation for type-safe handling of fruit data.
8-15: Well-Structured Sample Data
The exportedfruitsarray provides a diverse and representative dataset for testing and demonstration purposes. The use of appropriate values for each property helps ensure correct usage within tRPC endpoints and related features.examples/remix-trpc/pnpm-workspace.yaml (1)
1-4: Correct PNPM Workspace Configuration
The workspace configuration properly defines the two packages (./clientand./server), which is essential for managing dependencies and coordinating development in a monorepo. This file is concise and correctly formatted.examples/remix-trpc/client/tailwind.config.ts (1)
1-22: Configuration looks good!This is a well-structured Tailwind configuration that follows best practices. The content pattern properly includes Remix's client/server file conventions, and the font stack is comprehensive with appropriate fallbacks.
examples/remix-trpc/package.json (1)
1-12: Monorepo setup looks goodThe package.json correctly configures a monorepo structure with appropriate workspaces and a development script that concurrently runs both client and server.
examples/remix-trpc/.gitignore (1)
15-16:❓ Verification inconclusive
Consider committing the lock file
It's generally recommended to commit lock files to ensure consistent dependencies across all environments.
🏁 Script executed:
#!/bin/bash # Check how other examples in the repo handle lock files find examples -name "*.lock" -o -name "*lock.yaml" | grep -v "node_modules"Length of output: 75
Action Required: Confirm Lock File Strategy
The current
.gitignoreinexamples/remix-trpcexplicitly excludespnpm-lock.yaml(lines 15–16), which prevents committing the lock file. Our initial search using a general find command produced no results for committed lock files in the examples directory. This lack of evidence suggests that the repository may intentionally forgo committing lock files across its examples.Before proceeding, please manually verify whether this exclusion is by design or if committing the lock file is preferable for ensuring consistent dependency versions across environments. If opting to commit the lock file, consider removing or adjusting the exclusion rule in
.gitignore.examples/remix-trpc/server/src/routes/fruits.ts (1)
16-18: LGTM: Appropriate error handling for not found caseGood implementation of the 404 error handling when a fruit is not found.
examples/remix-trpc/server/tsconfig.json (1)
2-9: LGTM: Appropriate TypeScript configurationThe TypeScript configuration is well-structured with modern module settings ("NodeNext") and appropriate strict type-checking options enabled. This configuration works well for a Node.js application using ES modules.
examples/remix-trpc/server/src/trpc/index.ts (1)
10-13: LGTM: Clean implementation of getFruits procedureThe procedure correctly returns the fruits data from the model.
examples/remix-trpc/client/vite.config.ts (2)
5-9: Well-implemented module augmentation for Remix future flagsThe module augmentation correctly extends the
@remix-run/nodemodule to support thev3_singleFetchfuture flag, ensuring proper TypeScript type checking.
11-24: Good configuration with forward-looking Remix featuresThe Vite configuration is well structured and uses Remix's v3 future flags, positioning this example for future compatibility. The inclusion of
tsconfigPaths()ensures proper module resolution with the TypeScript path mappings.examples/remix-trpc/client/app/routes/_index.tsx (2)
5-5: LGTM - Effective use of tRPC with React QueryThe component correctly uses tRPC's integration with React Query to fetch the list of fruits, destructuring the necessary properties for handling different states.
7-9: Well-handled loading and error statesGood job handling the loading, error, and empty data states before rendering the main content.
examples/remix-trpc/client/tsconfig.json (3)
2-9: Comprehensive file inclusion patternThe configuration correctly includes TypeScript files from the main directory and specialized
.serverand.clientsubdirectories, which aligns with Remix's architecture patterns.
11-27: Well-configured TypeScript compiler optionsThe compiler options are appropriately set for a modern Remix application, with the correct libraries, module settings, and path mappings.
17-17:❓ Verification inconclusive
Verify compatibility with "Bundler" moduleResolution
The "Bundler" moduleResolution is a newer feature (TypeScript 5.0+) and might cause compatibility issues with older tooling that hasn't been updated.
🌐 Web query:
What TypeScript version introduced moduleResolution "Bundler" and are there any compatibility issues?💡 Result:
TypeScript introduced the
"bundler"module resolution strategy in version 5.0, released in March 2023[3][6][9]. This feature was designed to model how modern bundlers like Webpack, Vite, and esbuild handle module resolution, allowing extensionless imports and hybrid CommonJS/ESM resolution[6][9][10].
Key Compatibility Considerations
Tooling Support Issues
- ts-node: Fails to resolve imports without file extensions under
"bundler", requiring workarounds like forcing"module": "commonjs"in compiler options or switching to SWC transpilation[1].- SonarQube: Version 9.9 LTS lacked support, rejecting
"bundler"with errors until v10.1 added TypeScript 5 compatibility[2].- Nuxt Content: Incompatible as of December 2023, with planned future support[8].
Environment Configuration Challenges
- Monorepos: WebStorm/IntelliJ requires matching TypeScript versions across monorepo roots and subprojects. Mixing TS 4.x and 5.x causes resolution failures[7].
- Library Publishing: Using
"bundler"hides Node.js compatibility issues for consumers not using bundlers. The TypeScript team recommends"node16"/"nodenext"for libraries to ensure broader compatibility[4][6].Configuration Syncing
Projects using"bundler"must pair it with either:
"module": "esnext"(original requirement)"module": "preserve"(new in TS 5.4, enablesrequire()support)[3]
Recommended Practices
- Use
"bundler"only for applications with a bundler pipeline[4][10].- For libraries, prefer
"node16"/"nodenext"resolution to avoid ecosystem compatibility risks[4][6].- Verify toolchain compatibility before upgrading, as linters, test runners, and IDE integrations often lag behind TS releases[1][2][7].
The
"bundler"option streamlines development in modern build environments but requires careful toolchain coordination to avoid runtime resolution errors[3][4][6].Citations:
- 1: TypeStrong/ts-node#2034
- 2: https://community.sonarsource.com/t/sonarjs-typescript-5-moduleresolution-bundler-support/91581
- 3: https://devblogs.microsoft.com/typescript/announcing-typescript-5-4/
- 4: https://www.typescriptlang.org/docs/handbook/modules/guides/choosing-compiler-options.html
- 5: https://youtrack.jetbrains.com/issue/WEB-59200/TypeScript-5.0-Dont-add-.js-extension-if-moduleResolution-bundler
- 6: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html
- 7: https://intellij-support.jetbrains.com/hc/en-us/community/posts/24251106330642-moduleResolution-Bundler
- 8: nuxt/content#2469
- 9: https://devblogs.microsoft.com/typescript/announcing-typescript-5-0/
- 10: https://www.typescriptlang.org/tsconfig/moduleResolution.html
Action Required: Verify Toolchain Compatibility with "Bundler" Module Resolution
The
"bundler"module resolution strategy was introduced in TypeScript 5.0 (released in March 2023) and is best suited for modern bundler pipelines. However, be aware that:
- Some tooling (e.g.,
ts-node, older versions of SonarQube, or certain framework integrations like Nuxt Content) may not fully support this option without additional configuration or upgrades.- For library projects, using
"bundler"can mask Node.js compatibility issues for consumers; consider switching to"node16"or"nodenext"if broader compatibility is required.- In monorepo setups, ensure that all sub-projects are aligned with TypeScript 5.0+ to prevent resolution discrepancies.
Please confirm that your development and build tools are updated accordingly to avoid any unexpected runtime or integration issues.
examples/remix-trpc/client/app/routes/fruits.$id.tsx (2)
7-12: Good ID validation in loaderThe code properly extracts and validates the ID parameter, throwing an appropriate error response for invalid inputs.
14-18: Correct setup of tRPC server-side helpersThe server-side helpers are properly initialized with the router, an empty context, and the superjson transformer.
examples/remix-trpc/client/.eslintrc.cjs (2)
22-22: Verify ignorePatterns behaviorThe ignore patterns use negation (
!**/.server,!**/.client), which means "don't ignore these patterns." This is unusual as typically ignore patterns would exclude directories. Double-check if this is the intended behavior or if you meant to exclude these directories.
24-74: Solid ESLint foundationThe configuration provides a good foundation by extending recommended presets for various plugins. As the example evolves, consider adding more project-specific rules.
examples/remix-trpc/README.md (2)
53-65: Verify asset pathsThe screenshots reference assets like './assets/demo1.png' and './assets/code1.png'. Make sure these paths are correct and that the asset files are included in the PR.
🧰 Tools
🪛 LanguageTool
[uncategorized] ~59-~59: You might be missing the article “the” here.
Context: ...RPC) Client-side type error when using wrong type: 
1-122: Well-documented example projectThe README provides comprehensive documentation of the example project, clearly explaining its features, structure, and implementation details. The dual rendering approach demonstration (CSR and SSR) is particularly valuable for users learning tRPC with Remix.
🧰 Tools
🪛 LanguageTool
[uncategorized] ~59-~59: You might be missing the article “the” here.
Context: ...RPC) Client-side type error when using wrong type: 
[grammar] ~81-~81: The verb “React” needs to be in the to-infinitive form.
Context: ...Pages that update frequently - When you need React Query's powerful features like caching ...(MISSING_TO_BEFORE_A_VERB)
[style] ~121-~121: Consider using a less frequent alternative to set your writing apart from others and make it sound more professional.
Context: ...hecking for API calls ## Contributing Feel free to submit issues or pull requests to impro...(FEEL_FREE_TO_STYLE_ME)
🪛 markdownlint-cli2 (0.17.2)
17-17: Fenced code blocks should have a language specified
null(MD040, fenced-code-language)
examples/remix-trpc/client/package.json (1)
1-49: Well-structured package.json with appropriate dependenciesThe package.json configuration is well-organized and includes all necessary dependencies for a Remix application with tRPC integration. The consistent versioning across related packages (Remix at ^2.16.5, tRPC at ^11.0.4) is good practice. The inclusion of React Query, SuperJSON, and Zod aligns perfectly with the PR objective of demonstrating tRPC integration with both CSR and SSR approaches.
examples/remix-trpc/client/app/entry.server.tsx (3)
16-39: Good implementation of the request handler with bot detectionThe main request handler correctly differentiates between bot and browser requests using the isbot library, which is important for proper SSR handling. This aligns with the PR objective of demonstrating SSR capabilities in the Remix example.
Note: The ESLint warning about too many parameters can be safely ignored as this signature is required by the Remix framework.
🧰 Tools
🪛 ESLint
[error] 16-16: Function 'handleRequest' has too many parameters (5). Maximum allowed is 3.
(max-params)
41-89: Correct implementation of bot request handlingThe bot request handler appropriately uses
onAllReadyto ensure complete rendering before sending the response, which is the recommended approach for search engine crawlers and other bots. The error handling and abort timeout implementation are also well done.🧰 Tools
🪛 ESLint
[error] 41-41: Function 'handleBotRequest' has too many parameters (4). Maximum allowed is 3.
(max-params)
91-139: Appropriate browser request handling with streaming supportThe browser request handler correctly uses
onShellReadyinstead ofonAllReadyto enable streaming HTML to browsers, which improves perceived performance by allowing the browser to start rendering before all data is loaded. This implementation showcases a key benefit of server-side rendering in Remix.🧰 Tools
🪛 ESLint
[error] 91-91: Function 'handleBrowserRequest' has too many parameters (4). Maximum allowed is 3.
(max-params)
| router.get('/:id', (req: Request, res: Response) => { | ||
| const fruitId = parseInt(req.params.id); | ||
| const fruit = fruits.find((f) => f.id === fruitId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Improve error handling for ID parameter
The current implementation uses parseInt without validating the input, which could lead to unexpected behavior with non-numeric IDs. Consider adding explicit validation to handle invalid inputs more gracefully.
router.get('/:id', (req: Request, res: Response) => {
- const fruitId = parseInt(req.params.id);
+ const id = req.params.id;
+
+ if (!/^\d+$/.test(id)) {
+ return res.status(400).json({ message: 'Invalid ID format. ID must be a number.' });
+ }
+
+ const fruitId = parseInt(id);
const fruit = fruits.find((f) => f.id === fruitId);📝 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.
| router.get('/:id', (req: Request, res: Response) => { | |
| const fruitId = parseInt(req.params.id); | |
| const fruit = fruits.find((f) => f.id === fruitId); | |
| router.get('/:id', (req: Request, res: Response) => { | |
| const id = req.params.id; | |
| if (!/^\d+$/.test(id)) { | |
| return res.status(400).json({ message: 'Invalid ID format. ID must be a number.' }); | |
| } | |
| const fruitId = parseInt(id); | |
| const fruit = fruits.find((f) => f.id === fruitId); |
| const queryClient = new QueryClient(); | ||
| const trpcClient = trpc.createClient({ | ||
| links: [ | ||
| httpBatchLink({ | ||
| url: 'http://localhost:3001/trpc', | ||
| transformer: superjson, | ||
| }), | ||
| ], | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Avoid hardcoding the server URL
The tRPC server URL is hardcoded, which limits flexibility across different environments. Consider using environment variables or a configuration system to make the URL configurable.
const queryClient = new QueryClient();
const trpcClient = trpc.createClient({
links: [
httpBatchLink({
- url: 'http://localhost:3001/trpc',
+ url: process.env.TRPC_API_URL || 'http://localhost:3001/trpc',
transformer: superjson,
}),
],
});Additionally, consider adding a basic QueryClient configuration with sensible defaults for caching and retries:
-const queryClient = new QueryClient();
+const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ staleTime: 5 * 60 * 1000, // 5 minutes
+ retry: 1,
+ refetchOnWindowFocus: false,
+ },
+ },
+});📝 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.
| const queryClient = new QueryClient(); | |
| const trpcClient = trpc.createClient({ | |
| links: [ | |
| httpBatchLink({ | |
| url: 'http://localhost:3001/trpc', | |
| transformer: superjson, | |
| }), | |
| ], | |
| }); | |
| const queryClient = new QueryClient({ | |
| defaultOptions: { | |
| queries: { | |
| staleTime: 5 * 60 * 1000, // 5 minutes | |
| retry: 1, | |
| refetchOnWindowFocus: false, | |
| }, | |
| }, | |
| }); | |
| const trpcClient = trpc.createClient({ | |
| links: [ | |
| httpBatchLink({ | |
| url: process.env.TRPC_API_URL || 'http://localhost:3001/trpc', | |
| transformer: superjson, | |
| }), | |
| ], | |
| }); |
| getFruitById: t.procedure.input(z.number()).query((opts) => { | ||
| const fruit = fruits.find((f) => f.id === opts.input); | ||
| if (!fruit) throw new Error('Not found'); | ||
| return fruit; | ||
| }), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Improve error handling in getFruitById procedure
The current error message is generic. Consider providing a more descriptive error message that includes the ID that wasn't found, and implement proper tRPC error handling:
getFruitById: t.procedure.input(z.number()).query((opts) => {
const fruit = fruits.find((f) => f.id === opts.input);
- if (!fruit) throw new Error('Not found');
+ if (!fruit) {
+ throw new TRPCError({
+ code: 'NOT_FOUND',
+ message: `Fruit with ID ${opts.input} not found`,
+ });
+ }
return fruit;
}),You'll need to import TRPCError:
-import { initTRPC } from '@trpc/server';
+import { initTRPC, TRPCError } from '@trpc/server';📝 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.
| getFruitById: t.procedure.input(z.number()).query((opts) => { | |
| const fruit = fruits.find((f) => f.id === opts.input); | |
| if (!fruit) throw new Error('Not found'); | |
| return fruit; | |
| }), | |
| import { initTRPC, TRPCError } from '@trpc/server'; | |
| // ... other imports | |
| // ... other code | |
| const t = initTRPC.create(); | |
| // Assuming other procedures are defined above | |
| export const appRouter = t.router({ | |
| // ... other procedures | |
| getFruitById: t.procedure.input(z.number()).query((opts) => { | |
| const fruit = fruits.find((f) => f.id === opts.input); | |
| if (!fruit) { | |
| throw new TRPCError({ | |
| code: 'NOT_FOUND', | |
| message: `Fruit with ID ${opts.input} not found`, | |
| }); | |
| } | |
| return fruit; | |
| }), | |
| // ... additional procedures | |
| }); |
| const queryClient = new QueryClient(); | ||
|
|
||
| const trpcClient = trpc.createClient({ | ||
| links: [ | ||
| httpBatchLink({ | ||
| url: 'http://localhost:3010/trpc', | ||
| transformer: superjson, | ||
| }), | ||
| ], | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Memoize client instances to prevent recreation on each render
The QueryClient and tRPC client instances are created within the component, causing them to be recreated on each render. This can lead to issues with caching and state management.
Consider using React.useMemo or create these instances outside the component:
+const queryClient = new QueryClient();
+
+const trpcClient = trpc.createClient({
+ links: [
+ httpBatchLink({
+ url: 'http://localhost:3010/trpc',
+ transformer: superjson,
+ }),
+ ],
+});
export default function App() {
- const queryClient = new QueryClient();
-
- const trpcClient = trpc.createClient({
- links: [
- httpBatchLink({
- url: 'http://localhost:3010/trpc',
- transformer: superjson,
- }),
- ],
- });
return (
<trpc.Provider client={trpcClient} queryClient={queryClient}>📝 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.
| const queryClient = new QueryClient(); | |
| const trpcClient = trpc.createClient({ | |
| links: [ | |
| httpBatchLink({ | |
| url: 'http://localhost:3010/trpc', | |
| transformer: superjson, | |
| }), | |
| ], | |
| }); | |
| const queryClient = new QueryClient(); | |
| const trpcClient = trpc.createClient({ | |
| links: [ | |
| httpBatchLink({ | |
| url: 'http://localhost:3010/trpc', | |
| transformer: superjson, | |
| }), | |
| ], | |
| }); | |
| export default function App() { | |
| return ( | |
| <trpc.Provider client={trpcClient} queryClient={queryClient}> | |
| {/* ...rest of the component */} | |
| </trpc.Provider> | |
| ); | |
| } |
| "@remix-run/node": "^2.16.5", | ||
| "@remix-run/react": "^2.16.5", | ||
| "@remix-run/serve": "^2.16.5", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
React Router 7? https://github.com/juliusmarminge/rr7-trpc
8683b98 to
b725d83
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
examples/remix-trpc/client/app/routes/fruits.$id.tsx (1)
6-6: Consider server-side isolation pattern.Directly importing the
appRouterfrom the server package might cause issues in production builds by potentially leaking server-side code to the client.Consider setting up a dedicated API endpoint for the server and adjusting this implementation to use a client-side adapter instead of importing server components directly.
- import { appRouter } from "../../../server/src/trpc/index.js"; + // Use a dedicated client adapter instead of direct server imports + import { trpcClient } from '../lib/trpc-server-adapter';
🧹 Nitpick comments (6)
examples/remix-trpc/client/app/entry.server.tsx (2)
17-40: Well-implemented request handler with proper bot detection.The function correctly uses
isbotto determine the request type and delegates to the appropriate handler. This is essential for providing optimal SSR experiences for both bots (SEO) and real users.Consider adding a brief comment explaining why bots and browsers are handled differently (SEO considerations for bots vs. faster shell rendering for browsers).
🧰 Tools
🪛 ESLint
[error] 17-17: Function 'handleRequest' has too many parameters (5). Maximum allowed is 3.
(max-params)
15-15: Consider making ABORT_DELAY configurable.The hard-coded timeout of 5 seconds might not be suitable for all environments. In production with potentially slower data-fetching operations, this could lead to prematurely aborted requests.
Consider making this value configurable through an environment variable:
-const ABORT_DELAY = 5_000; +const ABORT_DELAY = process.env.ABORT_DELAY ? parseInt(process.env.ABORT_DELAY, 10) : 5_000;Also applies to: 88-88, 138-138
examples/remix-trpc/client/app/routes/fruits.$id.tsx (1)
21-27: Improve error handling in catch block.While the error handling correctly returns a 404 response, the error logging could be more informative for debugging purposes.
Consider enhancing the error logging:
try { const fruit = await helpers.getFruitById.fetch(id); return ({ fruit }); } catch (error) { - console.error(`Error loading fruit ${id}:`, error); + console.error(`Error loading fruit ${id}:`, error instanceof Error ? error.message : String(error)); throw new Response("Fruit not found", { status: 404 }); }examples/remix-trpc/README.md (3)
15-34: Specify language for the fenced code block.Adding a language identifier to the code block will improve syntax highlighting in GitHub and other platforms.
-``` +```text remix-trpc/ ├── client/ # Remix application │ ├── app/🧰 Tools
🪛 markdownlint-cli2 (0.17.2)
17-17: Fenced code blocks should have a language specified
null(MD040, fenced-code-language)
70-112: Comprehensive explanation of rendering approaches.The section clearly explains the two rendering approaches (CSR and SSR) with relevant code examples and use cases for each.
Minor grammar issue on line 83:
- When you need React Query's powerful features + When you need to use React Query's powerful features🧰 Tools
🪛 LanguageTool
[grammar] ~83-~83: The verb “React” needs to be in the to-infinitive form.
Context: ...Pages that update frequently - When you need React Query's powerful features like caching ...(MISSING_TO_BEFORE_A_VERB)
113-123: Clear explanation of type safety benefits.The section effectively highlights the value of end-to-end type safety, which is a key benefit of using tRPC.
Consider making the contributing section more specific:
-Feel free to submit issues or pull requests to improve this example. +Please submit issues or pull requests if you have suggestions for improving this example.🧰 Tools
🪛 LanguageTool
[style] ~122-~122: Consider using a less frequent alternative to set your writing apart from others and make it sound more professional.
Context: ...hecking for API calls ## Contributing Feel free to submit issues or pull requests to impro...(FEEL_FREE_TO_STYLE_ME)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (9)
examples/remix-trpc/assets/code1.pngis excluded by!**/*.pngexamples/remix-trpc/assets/code2.pngis excluded by!**/*.pngexamples/remix-trpc/assets/demo1.pngis excluded by!**/*.pngexamples/remix-trpc/assets/demo2.pngis excluded by!**/*.pngexamples/remix-trpc/assets/demo3.pngis excluded by!**/*.pngexamples/remix-trpc/client/public/favicon.icois excluded by!**/*.icoexamples/remix-trpc/client/public/logo-dark.pngis excluded by!**/*.pngexamples/remix-trpc/client/public/logo-light.pngis excluded by!**/*.pngpnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (22)
examples/remix-trpc/.gitignore(1 hunks)examples/remix-trpc/README.md(1 hunks)examples/remix-trpc/client/.eslintrc.cjs(1 hunks)examples/remix-trpc/client/app/entry.client.tsx(1 hunks)examples/remix-trpc/client/app/entry.server.tsx(1 hunks)examples/remix-trpc/client/app/lib/trpc.ts(1 hunks)examples/remix-trpc/client/app/root.tsx(1 hunks)examples/remix-trpc/client/app/routes/_index.tsx(1 hunks)examples/remix-trpc/client/app/routes/fruits.$id.tsx(1 hunks)examples/remix-trpc/client/app/tailwind.css(1 hunks)examples/remix-trpc/client/package.json(1 hunks)examples/remix-trpc/client/postcss.config.js(1 hunks)examples/remix-trpc/client/tailwind.config.ts(1 hunks)examples/remix-trpc/client/tsconfig.json(1 hunks)examples/remix-trpc/client/vite.config.ts(1 hunks)examples/remix-trpc/package.json(1 hunks)examples/remix-trpc/pnpm-workspace.yaml(1 hunks)examples/remix-trpc/server/package.json(1 hunks)examples/remix-trpc/server/src/index.ts(1 hunks)examples/remix-trpc/server/src/models/fruit.ts(1 hunks)examples/remix-trpc/server/src/trpc/index.ts(1 hunks)examples/remix-trpc/server/tsconfig.json(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (19)
- examples/remix-trpc/client/app/tailwind.css
- examples/remix-trpc/server/src/index.ts
- examples/remix-trpc/pnpm-workspace.yaml
- examples/remix-trpc/client/tailwind.config.ts
- examples/remix-trpc/client/postcss.config.js
- examples/remix-trpc/.gitignore
- examples/remix-trpc/package.json
- examples/remix-trpc/client/app/entry.client.tsx
- examples/remix-trpc/client/tsconfig.json
- examples/remix-trpc/client/app/routes/_index.tsx
- examples/remix-trpc/server/tsconfig.json
- examples/remix-trpc/client/.eslintrc.cjs
- examples/remix-trpc/client/vite.config.ts
- examples/remix-trpc/server/src/trpc/index.ts
- examples/remix-trpc/server/src/models/fruit.ts
- examples/remix-trpc/client/app/root.tsx
- examples/remix-trpc/client/app/lib/trpc.ts
- examples/remix-trpc/client/package.json
- examples/remix-trpc/server/package.json
🧰 Additional context used
🧬 Code Graph Analysis (1)
examples/remix-trpc/client/app/routes/fruits.$id.tsx (1)
packages/react-query/src/server/ssgProxy.ts (1)
createServerSideHelpers(82-224)
🪛 ESLint
examples/remix-trpc/client/app/entry.server.tsx
[error] 17-17: Function 'handleRequest' has too many parameters (5). Maximum allowed is 3.
(max-params)
[error] 42-42: Function 'handleBotRequest' has too many parameters (4). Maximum allowed is 3.
(max-params)
[error] 92-92: Function 'handleBrowserRequest' has too many parameters (4). Maximum allowed is 3.
(max-params)
🪛 LanguageTool
examples/remix-trpc/README.md
[uncategorized] ~60-~60: You might be missing the article “the” here.
Context: ...RPC) Client-side type error when using wrong type: 
[grammar] ~83-~83: The verb “React” needs to be in the to-infinitive form.
Context: ...Pages that update frequently - When you need React Query's powerful features like caching ...
(MISSING_TO_BEFORE_A_VERB)
[style] ~122-~122: Consider using a less frequent alternative to set your writing apart from others and make it sound more professional.
Context: ...hecking for API calls ## Contributing Feel free to submit issues or pull requests to impro...
(FEEL_FREE_TO_STYLE_ME)
🪛 markdownlint-cli2 (0.17.2)
examples/remix-trpc/README.md
17-17: Fenced code blocks should have a language specified
null
(MD040, fenced-code-language)
⏰ Context from checks skipped due to timeout of 90000ms (20)
- GitHub Check: E2E-tests (next-prisma-websockets-starter)
- GitHub Check: E2E-tests (.test/ssg)
- GitHub Check: E2E-tests (minimal-react)
- GitHub Check: E2E-tests (standalone-server)
- GitHub Check: E2E-tests (express-minimal)
- GitHub Check: E2E-tests (.test/diagnostics-big-router)
- GitHub Check: e2e-legacy-node (next-prisma-todomvc, 18.x)
- GitHub Check: E2E-tests (cloudflare-workers)
- GitHub Check: E2E-tests (fastify-server)
- GitHub Check: e2e-legacy-node (next-prisma-websockets-starter, 20.x)
- GitHub Check: e2e-legacy-node (next-prisma-starter, 20.x)
- GitHub Check: e2e-legacy-node (next-prisma-starter, 18.x)
- GitHub Check: e2e-legacy-node (next-prisma-websockets-starter, 18.x)
- GitHub Check: Release using pkg.pr.new
- GitHub Check: Test a monorepo using built declaration files
- GitHub Check: E2E-tests (Bun) (bun, ubuntu-latest)
- GitHub Check: E2E-tests (Deno) (deno-deploy)
- GitHub Check: test
- GitHub Check: Lint and auto-fix
- GitHub Check: Analyze (typescript)
🔇 Additional comments (9)
examples/remix-trpc/client/app/entry.server.tsx (3)
7-13: Good use of standard Remix server-side rendering setup.The code properly imports the necessary components from the correct packages, setting up the foundation for both bot and browser request handling with
renderToPipeableStream.
42-90: Correct implementation of bot request handling.The implementation properly waits for all content to be ready before sending the response (using
onAllReady), which is ideal for search engine crawlers that need complete content.🧰 Tools
🪛 ESLint
[error] 42-42: Function 'handleBotRequest' has too many parameters (4). Maximum allowed is 3.
(max-params)
92-140: Appropriate browser request handling with streaming response.The implementation correctly uses
onShellReadyrather than waiting for all content, providing a faster initial response for human users. The error handling and abort mechanism are well implemented.🧰 Tools
🪛 ESLint
[error] 92-92: Function 'handleBrowserRequest' has too many parameters (4). Maximum allowed is 3.
(max-params)
examples/remix-trpc/client/app/routes/fruits.$id.tsx (3)
8-13: Good parameter validation with appropriate error responses.The validation of the ID parameter is thorough, ensuring it exists and is a valid number. The error response includes a meaningful status code and message.
15-19: Appropriate use of createServerSideHelpers.The setup properly initializes the server-side helpers with the router, context, and transformer. This enables type-safe server-side data fetching.
30-46: Well-structured component with clean UI implementation.The component correctly uses
useLoaderDatato access the server-loaded data and presents it in a well-organized UI with proper styling. The back navigation link provides good UX.examples/remix-trpc/README.md (3)
1-14: Excellent introduction and feature overview.The README clearly introduces the example's purpose and highlights its key features, providing users with a good understanding of what to expect.
36-47: Clear and concise getting started instructions.The setup steps are well-documented and easy to follow, making it simple for new users to get the example running quickly.
49-67: Well-documented UI and type safety examples with screenshots.The screenshots effectively demonstrate both the UI experience and the type safety benefits, giving users a clear picture of what the example offers.
🧰 Tools
🪛 LanguageTool
[uncategorized] ~60-~60: You might be missing the article “the” here.
Context: ...RPC) Client-side type error when using wrong type: 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (5)
examples/remix-trpc/README.md (3)
17-17: Specify a language for the fenced code block.Adding a language specifier to the code block will improve syntax highlighting and follow markdown best practices.
-``` +```text🧰 Tools
🪛 markdownlint-cli2 (0.17.2)
17-17: Fenced code blocks should have a language specified
null(MD040, fenced-code-language)
59-59: Minor grammatical issue in sentence structure.Add "the" before "wrong type" for better readability.
-Client-side type error when using wrong type: +Client-side type error when using the wrong type:🧰 Tools
🪛 LanguageTool
[uncategorized] ~59-~59: You might be missing the article “the” here.
Context: ...RPC) Client-side type error when using wrong type: 
81-81: Grammatical correction needed in feature description.Add "to" before the verb "React" to follow proper English syntax.
-When you need React Query's powerful features like caching and refetching +When you need to use React Query's powerful features like caching and refetching🧰 Tools
🪛 LanguageTool
[grammar] ~81-~81: The verb “React” needs to be in the to-infinitive form.
Context: ...Pages that update frequently - When you need React Query's powerful features like caching ...(MISSING_TO_BEFORE_A_VERB)
examples/remix-trpc/client/app/entry.server.tsx (2)
41-89: Code duplication between bot and browser handlers.There's significant duplication between
handleBotRequestandhandleBrowserRequest. While these functions are following Remix conventions, consider extracting common logic to reduce duplication.-function handleBotRequest( - request: Request, - responseStatusCode: number, - responseHeaders: Headers, - remixContext: EntryContext, -) { - return new Promise((resolve, reject) => { - let shellRendered = false; - const { pipe, abort } = renderToPipeableStream( - <RemixServer - context={remixContext} - url={request.url} - abortDelay={ABORT_DELAY} - />, - { - onAllReady() { - shellRendered = true; - const body = new PassThrough(); - const stream = createReadableStreamFromReadable(body); - - responseHeaders.set('Content-Type', 'text/html'); - - resolve( - new Response(stream, { - headers: responseHeaders, - status: responseStatusCode, - }), - ); - - pipe(body); - }, - onShellError(error: unknown) { - reject(error); - }, - onError(error: unknown) { - responseStatusCode = 500; - // Log streaming rendering errors from inside the shell. Don't log - // errors encountered during initial shell rendering since they'll - // reject and get logged in handleDocumentRequest. - if (shellRendered) { - console.error(error); - } - }, - }, - ); - - setTimeout(abort, ABORT_DELAY); - }); -} - -function handleBrowserRequest( - request: Request, - responseStatusCode: number, - responseHeaders: Headers, - remixContext: EntryContext, -) { - return new Promise((resolve, reject) => { - let shellRendered = false; - const { pipe, abort } = renderToPipeableStream( - <RemixServer - context={remixContext} - url={request.url} - abortDelay={ABORT_DELAY} - />, - { - onShellReady() { - shellRendered = true; - const body = new PassThrough(); - const stream = createReadableStreamFromReadable(body); - - responseHeaders.set('Content-Type', 'text/html'); - - resolve( - new Response(stream, { - headers: responseHeaders, - status: responseStatusCode, - }), - ); - - pipe(body); - }, - onShellError(error: unknown) { - reject(error); - }, - onError(error: unknown) { - responseStatusCode = 500; - // Log streaming rendering errors from inside the shell. Don't log - // errors encountered during initial shell rendering since they'll - // reject and get logged in handleDocumentRequest. - if (shellRendered) { - console.error(error); - } - }, - }, - ); - - setTimeout(abort, ABORT_DELAY); - }); -} +function createRequestHandler( + readyCallback: 'onAllReady' | 'onShellReady', + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext, +) { + return new Promise((resolve, reject) => { + let shellRendered = false; + const { pipe, abort } = renderToPipeableStream( + <RemixServer + context={remixContext} + url={request.url} + abortDelay={ABORT_DELAY} + />, + { + [readyCallback]() { + shellRendered = true; + const body = new PassThrough(); + const stream = createReadableStreamFromReadable(body); + + responseHeaders.set('Content-Type', 'text/html'); + + resolve( + new Response(stream, { + headers: responseHeaders, + status: responseStatusCode, + }), + ); + + pipe(body); + }, + onShellError(error: unknown) { + reject(error); + }, + onError(error: unknown) { + responseStatusCode = 500; + // Log streaming rendering errors from inside the shell. Don't log + // errors encountered during initial shell rendering since they'll + // reject and get logged in handleDocumentRequest. + if (shellRendered) { + console.error(error); + } + }, + }, + ); + + setTimeout(abort, ABORT_DELAY); + }); +} + +function handleBotRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext, +) { + return createRequestHandler('onAllReady', request, responseStatusCode, responseHeaders, remixContext); +} + +function handleBrowserRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext, +) { + return createRequestHandler('onShellReady', request, responseStatusCode, responseHeaders, remixContext); +}Also applies to: 91-139
🧰 Tools
🪛 ESLint
[error] 41-41: Function 'handleBotRequest' has too many parameters (4). Maximum allowed is 3.
(max-params)
87-88: Consider variable timeout based on environment.Using a fixed timeout might be appropriate for development but consider making it configurable based on the environment for production use.
- setTimeout(abort, ABORT_DELAY); + // Allow configurable timeout in production environments + const timeoutMs = process.env.NODE_ENV === 'production' + ? (process.env.SERVER_ABORT_TIMEOUT || ABORT_DELAY) + : ABORT_DELAY; + setTimeout(abort, typeof timeoutMs === 'string' ? parseInt(timeoutMs, 10) : timeoutMs);Also applies to: 137-138
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
examples/remix-trpc/README.md(1 hunks)examples/remix-trpc/client/.eslintrc.cjs(1 hunks)examples/remix-trpc/client/app/entry.client.tsx(1 hunks)examples/remix-trpc/client/app/entry.server.tsx(1 hunks)examples/remix-trpc/client/app/lib/trpc.ts(1 hunks)examples/remix-trpc/client/app/routes/fruits.$id.tsx(1 hunks)examples/remix-trpc/client/tailwind.config.ts(1 hunks)examples/remix-trpc/client/vite.config.ts(1 hunks)examples/remix-trpc/server/src/index.ts(1 hunks)examples/remix-trpc/server/src/models/fruit.ts(1 hunks)examples/remix-trpc/server/src/trpc/index.ts(1 hunks)examples/remix-trpc/server/tsconfig.json(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (10)
- examples/remix-trpc/client/tailwind.config.ts
- examples/remix-trpc/client/app/lib/trpc.ts
- examples/remix-trpc/server/src/models/fruit.ts
- examples/remix-trpc/server/src/index.ts
- examples/remix-trpc/client/.eslintrc.cjs
- examples/remix-trpc/client/vite.config.ts
- examples/remix-trpc/client/app/entry.client.tsx
- examples/remix-trpc/client/app/routes/fruits.$id.tsx
- examples/remix-trpc/server/tsconfig.json
- examples/remix-trpc/server/src/trpc/index.ts
🧰 Additional context used
🪛 LanguageTool
examples/remix-trpc/README.md
[uncategorized] ~59-~59: You might be missing the article “the” here.
Context: ...RPC) Client-side type error when using wrong type: 
[grammar] ~81-~81: The verb “React” needs to be in the to-infinitive form.
Context: ...Pages that update frequently - When you need React Query's powerful features like caching ...
(MISSING_TO_BEFORE_A_VERB)
[style] ~121-~121: Consider using a less frequent alternative to set your writing apart from others and make it sound more professional.
Context: ...hecking for API calls ## Contributing Feel free to submit issues or pull requests to impro...
(FEEL_FREE_TO_STYLE_ME)
🪛 markdownlint-cli2 (0.17.2)
examples/remix-trpc/README.md
17-17: Fenced code blocks should have a language specified
null
(MD040, fenced-code-language)
🪛 ESLint
examples/remix-trpc/client/app/entry.server.tsx
[error] 16-16: Function 'handleRequest' has too many parameters (5). Maximum allowed is 3.
(max-params)
[error] 41-41: Function 'handleBotRequest' has too many parameters (4). Maximum allowed is 3.
(max-params)
[error] 91-91: Function 'handleBrowserRequest' has too many parameters (4). Maximum allowed is 3.
(max-params)
⏰ Context from checks skipped due to timeout of 90000ms (20)
- GitHub Check: E2E-tests (next-minimal-starter)
- GitHub Check: E2E-tests (standalone-server)
- GitHub Check: E2E-tests (fastify-server)
- GitHub Check: E2E-tests (next-prisma-todomvc)
- GitHub Check: E2E-tests (express-server)
- GitHub Check: E2E-tests (minimal-react)
- GitHub Check: E2E-tests (.test/diagnostics-big-router)
- GitHub Check: E2E-tests (cloudflare-workers)
- GitHub Check: e2e-legacy-node (next-prisma-todomvc, 18.x)
- GitHub Check: e2e-legacy-node (next-prisma-todomvc, 20.x)
- GitHub Check: e2e-legacy-node (next-prisma-websockets-starter, 18.x)
- GitHub Check: e2e-legacy-node (next-prisma-starter, 18.x)
- GitHub Check: e2e-legacy-node (next-prisma-websockets-starter, 20.x)
- GitHub Check: Test a monorepo using built declaration files
- GitHub Check: e2e-legacy-node (next-prisma-starter, 20.x)
- GitHub Check: Release using pkg.pr.new
- GitHub Check: E2E-tests (Bun) (bun, ubuntu-latest)
- GitHub Check: E2E-tests (Deno) (deno-deploy)
- GitHub Check: test
- GitHub Check: Lint and auto-fix
🔇 Additional comments (3)
examples/remix-trpc/README.md (1)
1-122: Great README with comprehensive documentation!The README provides excellent documentation covering the key aspects of using tRPC with Remix. It clearly explains the monorepo structure, demonstrates both CSR and SSR approaches, and highlights the type safety benefits. The project structure diagram, screenshots, and code examples make it easy for users to understand and implement the pattern.
🧰 Tools
🪛 LanguageTool
[uncategorized] ~59-~59: You might be missing the article “the” here.
Context: ...RPC) Client-side type error when using wrong type: 
[grammar] ~81-~81: The verb “React” needs to be in the to-infinitive form.
Context: ...Pages that update frequently - When you need React Query's powerful features like caching ...(MISSING_TO_BEFORE_A_VERB)
[style] ~121-~121: Consider using a less frequent alternative to set your writing apart from others and make it sound more professional.
Context: ...hecking for API calls ## Contributing Feel free to submit issues or pull requests to impro...(FEEL_FREE_TO_STYLE_ME)
🪛 markdownlint-cli2 (0.17.2)
17-17: Fenced code blocks should have a language specified
null(MD040, fenced-code-language)
examples/remix-trpc/client/app/entry.server.tsx (2)
1-140: Well-structured server entry file following Remix conventions.The implementation correctly handles both bot and browser requests with appropriate rendering strategies for each. For bots, it waits for all content to be ready (beneficial for SEO), while for browsers it streams the response as soon as the shell is ready (beneficial for user experience). The error handling is also well implemented.
🧰 Tools
🪛 ESLint
[error] 16-16: Function 'handleRequest' has too many parameters (5). Maximum allowed is 3.
(max-params)
[error] 41-41: Function 'handleBotRequest' has too many parameters (4). Maximum allowed is 3.
(max-params)
[error] 91-91: Function 'handleBrowserRequest' has too many parameters (4). Maximum allowed is 3.
(max-params)
16-39:❓ Verification inconclusive
The
handleRequestfunction follows Remix conventions correctly.The function properly differentiates between bot and browser requests using the
isbotpackage, which is important for SEO optimization. The parameters follow the standard Remix entry server function signature.
🏁 Script executed:
#!/bin/bash # Check if the isbot package is properly imported and used in similar Remix examples fd --type f -e ts -e tsx "isbot" | grep -v "node_modules" | xargs cat | grep "isbot"Length of output: 80
Below is an updated review comment. Since our initial automated search for the usage of the
isbotpackage in the codebase returned no output, please verify manually whether the package is properly imported—in particular in the fileexamples/remix-trpc/client/app/entry.server.tsx(it may be imported above line 16, which was not covered in the snippet). Once its import is confirmed and the behavior remains as described, the function indeed follows Remix conventions appropriately.Rewritten Review Comment:
Please verify the
isbotimport for proper bot detectionThe
handleRequestfunction inexamples/remix-trpc/client/app/entry.server.tsxcorrectly differentiates between bot and browser requests in line with Remix conventions, which is important for SEO optimization. However, our automated check did not conclusively show an explicit import of theisbotpackage—this may be due to its location outside the snippet (e.g., above line 16). Please manually confirm thatisbotis correctly imported (or provided globally if that’s intended), so that its usage in the function is properly supported.
- Verify that
import isbot from 'isbot'or its equivalent appears inexamples/remix-trpc/client/app/entry.server.tsx(or another appropriate file).- Confirm that the use of
isbot(request.headers.get('user-agent') || '')correctly differentiates bot and browser requests.Once these details are validated, the changes should be safe to approve.
To further assist, you can run the following shell script to check for the
isbotimport in the file:#!/bin/bash grep -En "import.*isbot" examples/remix-trpc/client/app/entry.server.tsx || echo "No import found for 'isbot'" grep -En "isbot" examples/remix-trpc/client/app/entry.server.tsx || echo "No occurrence of 'isbot' found"🧰 Tools
🪛 ESLint
[error] 16-16: Function 'handleRequest' has too many parameters (5). Maximum allowed is 3.
(max-params)
Closes #
🎯 Changes
Add Remix + tRPC example project
This PR adds a new example showcasing how to use tRPC with Remix, demonstrating both client-side rendering (CSR) and server-side rendering (SSR) approaches within the same application. The example includes:
✅ Checklist
Summary by CodeRabbit
New Features
Documentation
Chores