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
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ CACHE_DATABASE_PATH="./other/cache.db"
SESSION_SECRET="super-duper-s3cret"
HONEYPOT_SECRET="super-duper-s3cret"
RESEND_API_KEY="re_blAh_blaHBlaHblahBLAhBlAh"
SENTRY_DSN="your-dsn"
SENTRY_DSN="https://be85ef67af06663b750a36fd66223526@o4505900870467584.ingest.us.sentry.io/4510528957317120"
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

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

The Sentry DSN is exposed in the .env.example file. This appears to be a real DSN token that could be used to send data to your Sentry project. Even for example files, it's better to use a placeholder value to prevent accidental usage of production/test credentials.

Suggested change
SENTRY_DSN="https://be85ef67af06663b750a36fd66223526@o4505900870467584.ingest.us.sentry.io/4510528957317120"
SENTRY_DSN="<your-sentry-dsn-here>"

Copilot uses AI. Check for mistakes.
SENTRY_ENVIRONMENT="development"

# this is set to a random value in the Dockerfile
INTERNAL_COMMAND_TOKEN="some-made-up-token"
Expand Down
21 changes: 21 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -231,13 +231,23 @@ jobs:
--regions $FLY_REGION \
--vm-size shared-cpu-1x \
--env APP_ENV=staging \
--env SENTRY_ENVIRONMENT=staging \
--env ALLOW_INDEXING=false \
--app $FLY_APP_NAME \
--image-label ${{ github.sha }} \
--build-arg COMMIT_SHA=${{ github.sha }} \
--build-secret SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} \

echo "url=https://$FLY_APP_NAME.fly.dev" >> $GITHUB_OUTPUT
- name: Create Sentry release
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: kasper-peulen-d2924a93
SENTRY_PROJECT: epic-rsc-stack
Comment on lines +245 to +246
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

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

The Sentry organization and project identifiers are hardcoded in the GitHub Actions workflow. These should be extracted to GitHub repository variables or secrets to make this workflow reusable across different projects and prevent accidental deployments to the wrong Sentry project.

Suggested change
SENTRY_ORG: kasper-peulen-d2924a93
SENTRY_PROJECT: epic-rsc-stack
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT }}

Copilot uses AI. Check for mistakes.
if: ${{ env.SENTRY_AUTH_TOKEN }}
uses: getsentry/action-release@v3
with:
environment: staging

cleanup-staging:
name: 🧹 Cleanup staging app
Expand Down Expand Up @@ -290,4 +300,15 @@ jobs:
- name: 🚀 Deploy Production
run: |
flyctl deploy \
--env SENTRY_ENVIRONMENT=production \
--image "registry.fly.io/${{ steps.app_name.outputs.value }}:${{ github.sha }}"

- name: Create Sentry release
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: kasper-peulen-d2924a93
SENTRY_PROJECT: epic-rsc-stack
Comment on lines +309 to +310
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

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

The Sentry organization and project identifiers are hardcoded in the production deployment step. These should be extracted to GitHub repository variables or secrets to make this workflow reusable across different projects and prevent accidental deployments to the wrong Sentry project.

Suggested change
SENTRY_ORG: kasper-peulen-d2924a93
SENTRY_PROJECT: epic-rsc-stack
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT }}

Copilot uses AI. Check for mistakes.
if: ${{ env.SENTRY_AUTH_TOKEN }}
uses: getsentry/action-release@v3
with:
environment: production
40 changes: 37 additions & 3 deletions app/entry.client.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,44 @@
import * as Sentry from '@sentry/react-router'
import { startTransition } from 'react'
import { hydrateRoot } from 'react-dom/client'
import { HydratedRouter } from 'react-router/dom'

if (ENV.MODE === 'production' && ENV.SENTRY_DSN) {
void import('./utils/monitoring.client.tsx').then(({ init }) => init())
}
Sentry.init({
// Sentry will only send requests if SENTRY_DSN is defined
dsn: ENV.MODE === 'production' ? ENV.SENTRY_DSN : undefined,
// See https://spotlightjs.com/ for how to install the Spotlight Desktop app for local development
spotlight: ENV.MODE === 'development',
environment: ENV.MODE,
beforeSend(event) {
if (event.request?.url) {
const url = new URL(event.request.url)
if (
url.protocol === 'chrome-extension:' ||
url.protocol === 'moz-extension:'
) {
// This error is from a browser extension, ignore it
return null
}
}
return event
},
integrations: [
Sentry.reactRouterTracingIntegration(),
Sentry.replayIntegration(),
],
Comment on lines +25 to +28
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

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

The browserProfilingIntegration was removed from the client-side Sentry initialization. If browser profiling was previously enabled and desired, verify this removal is intentional. If profiling is still needed, ensure it's configured correctly with the new v10 API.

Copilot uses AI. Check for mistakes.

// Set tracesSampleRate to 1.0 to capture 100%
// of transactions for performance monitoring.
// We recommend adjusting this value in production
tracesSampleRate: 1.0,

// Capture Replay for 10% of all sessions,
// plus for 100% of sessions with an error
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,

enableLogs: true,
})

startTransition(() => {
hydrateRoot(document, <HydratedRouter />)
Expand Down
36 changes: 10 additions & 26 deletions app/entry.server.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
import crypto from 'node:crypto'
import { PassThrough } from 'node:stream'
import { styleText } from 'node:util'
import { contentSecurity } from '@nichtsam/helmet/content'
import { createReadableStreamFromReadable } from '@react-router/node'
import * as Sentry from '@sentry/react-router'
import { isbot } from 'isbot'
import { renderToPipeableStream } from 'react-dom/server'
import {
ServerRouter,
type LoaderFunctionArgs,
type ActionFunctionArgs,
type HandleDocumentRequestFunction,
} from 'react-router'
import { type HandleDocumentRequestFunction, ServerRouter } from 'react-router'
import { getEnv, init } from './utils/env.server.ts'
import { getInstanceInfo } from './utils/litefs.server.ts'
import { NonceProvider } from './utils/nonce-provider.ts'
Expand All @@ -26,7 +20,7 @@ const MODE = process.env.NODE_ENV ?? 'development'

type DocRequestArgs = Parameters<HandleDocumentRequestFunction>

export default async function handleRequest(...args: DocRequestArgs) {
async function handleRequest(...args: DocRequestArgs) {
const [request, responseStatusCode, responseHeaders, reactRouterContext] =
args
const { currentInstance, primaryInstance } = await getInstanceInfo()
Expand Down Expand Up @@ -74,6 +68,10 @@ export default async function handleRequest(...args: DocRequestArgs) {
'connect-src': [
MODE === 'development' ? 'ws:' : undefined,
process.env.SENTRY_DSN ? '*.sentry.io' : undefined,
// Spotlight (SSE to the sidecar)
MODE === 'development'
? 'http://localhost:8969'
: undefined,
"'self'",
],
'font-src': ["'self'"],
Expand All @@ -96,7 +94,8 @@ export default async function handleRequest(...args: DocRequestArgs) {
status: didError ? 500 : responseStatusCode,
}),
)
pipe(body)
// this enables distributed tracing between client and server!
pipe(Sentry.getMetaTagTransformer(body))
},
onShellError: (err: unknown) => {
reject(err)
Expand All @@ -122,21 +121,6 @@ export async function handleDataRequest(response: Response) {
return response
}

export function handleError(
error: unknown,
{ request }: LoaderFunctionArgs | ActionFunctionArgs,
): void {
// Skip capturing if the request is aborted as Remix docs suggest
// Ref: https://remix.run/docs/en/main/file-conventions/entry.server#handleerror
if (request.signal.aborted) {
return
}

if (error instanceof Error) {
console.error(styleText('red', String(error.stack)))
} else {
console.error(error)
}
export const handleError = Sentry.createSentryHandleError({ logErrors: true })

Sentry.captureException(error)
}
export default Sentry.wrapSentryHandleRequest(handleRequest)
29 changes: 29 additions & 0 deletions app/routes/sentry/api.sentry-example-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import * as Sentry from '@sentry/react-router'
import { requireUserWithRole } from '../../utils/permissions.server.ts'
import { type Route } from './+types/api.sentry-example-api.ts'

class SentryExampleBackendError extends Error {
constructor(message: string | undefined) {
super(message)
this.name = 'SentryExampleBackendError'
}
}

export async function loader({ request }: Route.LoaderArgs) {
await requireUserWithRole(request, 'admin')

await Sentry.startSpan(
{
name: 'Example Backend Span',
op: 'test',
},
async () => {
// Simulate some backend work
await new Promise((resolve) => setTimeout(resolve, 100))
},
)

throw new SentryExampleBackendError(
'This error is raised on the backend API route.',
)
}
Comment on lines +1 to +29
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

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

The new Sentry example routes (example-page.tsx and api.sentry-example-api.ts) lack test coverage. Since these routes are intended to validate Sentry integration, consider adding tests to ensure they function correctly and that the error capturing works as expected.

Copilot uses AI. Check for mistakes.
Loading