Skip to content

Commit eab22b9

Browse files
committed
feat(remix): Add flags for Remix v2 usage.
1 parent 5fdaaa7 commit eab22b9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1176
-178
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,7 @@ jobs:
787787
'create-react-app',
788788
'create-next-app',
789789
'create-remix-app',
790+
'create-remix-app-v2',
790791
'nextjs-app-dir',
791792
'react-create-hash-router',
792793
'standard-frontend-react',

packages/e2e-tests/test-applications/create-next-app/playwright.config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import type { PlaywrightTestConfig } from '@playwright/test';
22
import { devices } from '@playwright/test';
33

4+
// Fix urls not resolving to localhost on Node v17+
5+
// See: https://github.com/axios/axios/issues/3821#issuecomment-1413727575
6+
import { setDefaultResultOrder } from 'dns';
7+
setDefaultResultOrder('ipv4first');
8+
49
const testEnv = process.env.TEST_ENV;
510

611
if (!testEnv) {
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/** @type {import('eslint').Linter.Config} */
2+
module.exports = {
3+
extends: ["@remix-run/eslint-config", "@remix-run/eslint-config/node"],
4+
};
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
node_modules
2+
3+
/.cache
4+
/build
5+
/public/build
6+
.env
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@sentry:registry=http://localhost:4873
2+
@sentry-internal:registry=http://localhost:4873
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Welcome to Remix!
2+
3+
- [Remix Docs](https://remix.run/docs)
4+
5+
## Development
6+
7+
From your terminal:
8+
9+
```sh
10+
npm run dev
11+
```
12+
13+
This starts your app in development mode, rebuilding assets on file changes.
14+
15+
## Deployment
16+
17+
First, build your app for production:
18+
19+
```sh
20+
npm run build
21+
```
22+
23+
Then run the app in production mode:
24+
25+
```sh
26+
npm start
27+
```
28+
29+
Now you'll need to pick a host to deploy it to.
30+
31+
### DIY
32+
33+
If you're familiar with deploying node applications, the built-in Remix app server is production-ready.
34+
35+
Make sure to deploy the output of `remix build`
36+
37+
- `build/`
38+
- `public/build/`
39+
40+
### Using a Template
41+
42+
When you ran `npx create-remix@latest` there were a few choices for hosting. You can run that again to create a new project, then copy over relevant code/assets from your current app to the new project that's pre-configured for your target server.
43+
44+
Most importantly, this means everything in the `app/` directory, but if you've further customized your current application outside of there it may also include:
45+
46+
- Any assets you've added/updated in `public/`
47+
- Any updated versions of root files such as `.eslintrc.js`, etc.
48+
49+
```sh
50+
cd ..
51+
# create a new project, and pick a pre-configured host
52+
npx create-remix@latest
53+
cd my-new-remix-app
54+
# remove the new project's app (not the old one!)
55+
rm -rf app
56+
# copy your app over
57+
cp -R ../my-old-remix-app/app app
58+
```
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* By default, Remix will handle hydrating your app on the client for you.
3+
* You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨
4+
* For more information, see https://remix.run/file-conventions/entry.client
5+
*/
6+
7+
import { RemixBrowser, useLocation, useMatches } from '@remix-run/react';
8+
import { startTransition, StrictMode, useEffect } from 'react';
9+
import { hydrateRoot } from 'react-dom/client';
10+
import * as Sentry from '@sentry/remix';
11+
12+
Sentry.init({
13+
dsn: window.ENV.SENTRY_DSN,
14+
integrations: [
15+
new Sentry.BrowserTracing({
16+
routingInstrumentation: Sentry.remixRouterInstrumentation(useEffect, useLocation, useMatches),
17+
}),
18+
new Sentry.Replay(),
19+
],
20+
// Performance Monitoring
21+
tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production!
22+
// Session Replay
23+
replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production.
24+
replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur.
25+
});
26+
27+
Sentry.addGlobalEventProcessor(event => {
28+
if (
29+
event.type === 'transaction' &&
30+
(event.contexts?.trace?.op === 'pageload' || event.contexts?.trace?.op === 'navigation')
31+
) {
32+
const eventId = event.event_id;
33+
if (eventId) {
34+
window.recordedTransactions = window.recordedTransactions || [];
35+
window.recordedTransactions.push(eventId);
36+
}
37+
}
38+
39+
return event;
40+
});
41+
42+
startTransition(() => {
43+
hydrateRoot(
44+
document,
45+
<StrictMode>
46+
<RemixBrowser />
47+
</StrictMode>,
48+
);
49+
});
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/**
2+
* By default, Remix will handle generating the HTTP Response for you.
3+
* You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨
4+
* For more information, see https://remix.run/file-conventions/entry.server
5+
*/
6+
7+
import { PassThrough } from 'node:stream';
8+
9+
import type { AppLoadContext, EntryContext } from '@remix-run/node';
10+
import { Response } from '@remix-run/node';
11+
import { RemixServer } from '@remix-run/react';
12+
import isbot from 'isbot';
13+
import { renderToPipeableStream } from 'react-dom/server';
14+
import * as Sentry from '@sentry/remix';
15+
16+
const ABORT_DELAY = 5_000;
17+
18+
Sentry.init({
19+
dsn: process.env.E2E_TEST_DSN,
20+
// Performance Monitoring
21+
tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production!
22+
isRemixV2: true,
23+
});
24+
25+
export default function handleRequest(
26+
request: Request,
27+
responseStatusCode: number,
28+
responseHeaders: Headers,
29+
remixContext: EntryContext,
30+
loadContext: AppLoadContext,
31+
) {
32+
return isbot(request.headers.get('user-agent'))
33+
? handleBotRequest(request, responseStatusCode, responseHeaders, remixContext)
34+
: handleBrowserRequest(request, responseStatusCode, responseHeaders, remixContext);
35+
}
36+
37+
function handleBotRequest(
38+
request: Request,
39+
responseStatusCode: number,
40+
responseHeaders: Headers,
41+
remixContext: EntryContext,
42+
) {
43+
return new Promise((resolve, reject) => {
44+
let shellRendered = false;
45+
const { pipe, abort } = renderToPipeableStream(
46+
<RemixServer context={remixContext} url={request.url} abortDelay={ABORT_DELAY} />,
47+
{
48+
onAllReady() {
49+
shellRendered = true;
50+
const body = new PassThrough();
51+
52+
responseHeaders.set('Content-Type', 'text/html');
53+
54+
resolve(
55+
new Response(body, {
56+
headers: responseHeaders,
57+
status: responseStatusCode,
58+
}),
59+
);
60+
61+
pipe(body);
62+
},
63+
onShellError(error: unknown) {
64+
reject(error);
65+
},
66+
onError(error: unknown) {
67+
responseStatusCode = 500;
68+
// Log streaming rendering errors from inside the shell. Don't log
69+
// errors encountered during initial shell rendering since they'll
70+
// reject and get logged in handleDocumentRequest.
71+
if (shellRendered) {
72+
console.error(error);
73+
}
74+
},
75+
},
76+
);
77+
78+
setTimeout(abort, ABORT_DELAY);
79+
});
80+
}
81+
82+
function handleBrowserRequest(
83+
request: Request,
84+
responseStatusCode: number,
85+
responseHeaders: Headers,
86+
remixContext: EntryContext,
87+
) {
88+
return new Promise((resolve, reject) => {
89+
let shellRendered = false;
90+
const { pipe, abort } = renderToPipeableStream(
91+
<RemixServer context={remixContext} url={request.url} abortDelay={ABORT_DELAY} />,
92+
{
93+
onShellReady() {
94+
shellRendered = true;
95+
const body = new PassThrough();
96+
97+
responseHeaders.set('Content-Type', 'text/html');
98+
99+
resolve(
100+
new Response(body, {
101+
headers: responseHeaders,
102+
status: responseStatusCode,
103+
}),
104+
);
105+
106+
pipe(body);
107+
},
108+
onShellError(error: unknown) {
109+
reject(error);
110+
},
111+
onError(error: unknown) {
112+
responseStatusCode = 500;
113+
// Log streaming rendering errors from inside the shell. Don't log
114+
// errors encountered during initial shell rendering since they'll
115+
// reject and get logged in handleDocumentRequest.
116+
if (shellRendered) {
117+
console.error(error);
118+
}
119+
},
120+
},
121+
);
122+
123+
setTimeout(abort, ABORT_DELAY);
124+
});
125+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { cssBundleHref } from '@remix-run/css-bundle';
2+
import { json, LinksFunction } from '@remix-run/node';
3+
import {
4+
Links,
5+
LiveReload,
6+
Meta,
7+
Outlet,
8+
Scripts,
9+
ScrollRestoration,
10+
useLoaderData,
11+
useRouteError,
12+
} from '@remix-run/react';
13+
import { captureRemixErrorBoundaryError, withSentry } from '@sentry/remix';
14+
15+
export const links: LinksFunction = () => [...(cssBundleHref ? [{ rel: 'stylesheet', href: cssBundleHref }] : [])];
16+
17+
export const loader = () => {
18+
return json({
19+
ENV: {
20+
SENTRY_DSN: process.env.E2E_TEST_DSN,
21+
},
22+
});
23+
};
24+
25+
export const ErrorBoundary = () => {
26+
const error = useRouteError() as { eventId?: string | Promise<string> | undefined };
27+
const eventId = captureRemixErrorBoundaryError(error);
28+
29+
return (
30+
<div>
31+
<span>ErrorBoundary Error</span>
32+
<span id="event-id">{eventId}</span>
33+
</div>
34+
);
35+
};
36+
37+
function App() {
38+
const { ENV } = useLoaderData();
39+
40+
return (
41+
<html lang="en">
42+
<head>
43+
<meta charSet="utf-8" />
44+
<meta name="viewport" content="width=device-width,initial-scale=1" />
45+
<script
46+
dangerouslySetInnerHTML={{
47+
__html: `window.ENV = ${JSON.stringify(ENV)}`,
48+
}}
49+
/>
50+
<Meta />
51+
<Links />
52+
</head>
53+
<body>
54+
<Outlet />
55+
<ScrollRestoration />
56+
<Scripts />
57+
<LiveReload />
58+
</body>
59+
</html>
60+
);
61+
}
62+
63+
export default withSentry(App, {
64+
wrapWithErrorBoundary: false,
65+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import * as Sentry from '@sentry/remix';
2+
import { Link } from '@remix-run/react';
3+
4+
export default function Index() {
5+
return (
6+
<div>
7+
<input
8+
type="button"
9+
value="Capture Exception"
10+
id="exception-button"
11+
onClick={() => {
12+
const eventId = Sentry.captureException(new Error('I am an error!'));
13+
window.capturedExceptionId = eventId;
14+
}}
15+
/>
16+
<Link to="/user/5" id="navigation">
17+
navigate
18+
</Link>
19+
</div>
20+
);
21+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { useState } from 'react';
2+
3+
export default function ErrorBoundaryCapture() {
4+
const [count, setCount] = useState(0);
5+
6+
if (count > 0) {
7+
throw new Error('Sentry React Component Error');
8+
} else {
9+
setTimeout(() => setCount(count + 1), 0);
10+
}
11+
12+
return <div>{count}</div>;
13+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { LoaderFunction } from '@remix-run/node';
2+
import { useLoaderData } from '@remix-run/react';
3+
4+
export const loader: LoaderFunction = async ({ params: { id } }) => {
5+
if (id === '-1') {
6+
throw new Error('Unexpected Server Error');
7+
}
8+
9+
return null;
10+
};
11+
12+
export default function LoaderError() {
13+
const data = useLoaderData();
14+
15+
return (
16+
<div>
17+
<h1>{data && data.test ? data.test : 'Not Found'}</h1>
18+
</div>
19+
);
20+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function User() {
2+
return <div>I am a blank page</div>;
3+
}

0 commit comments

Comments
 (0)