Skip to content

Commit 69b308d

Browse files
onurtemizkanLms24
andauthored
feat(remix): Add Remix 2.x release support. (#8940)
Co-authored-by: Lukas Stracke <[email protected]>
1 parent 252f450 commit 69b308d

Some content is hidden

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

41 files changed

+1242
-162
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -815,6 +815,7 @@ jobs:
815815
'create-react-app',
816816
'create-next-app',
817817
'create-remix-app',
818+
'create-remix-app-v2',
818819
'nextjs-app-dir',
819820
'react-create-hash-router',
820821
'react-router-6-use-routes',
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: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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
43+
project, then copy over relevant code/assets from your current app to the new project that's pre-configured for your
44+
target server.
45+
46+
Most importantly, this means everything in the `app/` directory, but if you've further customized your current
47+
application outside of there it may also include:
48+
49+
- Any assets you've added/updated in `public/`
50+
- Any updated versions of root files such as `.eslintrc.js`, etc.
51+
52+
```sh
53+
cd ..
54+
# create a new project, and pick a pre-configured host
55+
npx create-remix@latest
56+
cd my-new-remix-app
57+
# remove the new project's app (not the old one!)
58+
rm -rf app
59+
# copy your app over
60+
cp -R ../my-old-remix-app/app app
61+
```
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: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
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, DataFunctionArgs } from '@remix-run/node';
10+
import { createReadableStreamFromReadable } from '@remix-run/node';
11+
import { RemixServer } from '@remix-run/react';
12+
import { renderToPipeableStream } from 'react-dom/server';
13+
import * as Sentry from '@sentry/remix';
14+
import { installGlobals } from '@remix-run/node';
15+
import isbot from 'isbot';
16+
17+
installGlobals();
18+
19+
const ABORT_DELAY = 5_000;
20+
21+
Sentry.init({
22+
dsn: process.env.E2E_TEST_DSN,
23+
// Performance Monitoring
24+
tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production!
25+
});
26+
27+
export function handleError(error: unknown, { request }: DataFunctionArgs): void {
28+
Sentry.captureRemixServerException(error, 'remix.server', request);
29+
}
30+
31+
export default function handleRequest(
32+
request: Request,
33+
responseStatusCode: number,
34+
responseHeaders: Headers,
35+
remixContext: EntryContext,
36+
loadContext: AppLoadContext,
37+
) {
38+
return isbot(request.headers.get('user-agent'))
39+
? handleBotRequest(request, responseStatusCode, responseHeaders, remixContext)
40+
: handleBrowserRequest(request, responseStatusCode, responseHeaders, remixContext);
41+
}
42+
43+
function handleBotRequest(
44+
request: Request,
45+
responseStatusCode: number,
46+
responseHeaders: Headers,
47+
remixContext: EntryContext,
48+
) {
49+
return new Promise((resolve, reject) => {
50+
let shellRendered = false;
51+
const { pipe, abort } = renderToPipeableStream(
52+
<RemixServer context={remixContext} url={request.url} abortDelay={ABORT_DELAY} />,
53+
{
54+
onAllReady() {
55+
shellRendered = true;
56+
const body = new PassThrough();
57+
const stream = createReadableStreamFromReadable(body);
58+
59+
responseHeaders.set('Content-Type', 'text/html');
60+
61+
resolve(
62+
new Response(stream, {
63+
headers: responseHeaders,
64+
status: responseStatusCode,
65+
}),
66+
);
67+
68+
pipe(body);
69+
},
70+
onShellError(error: unknown) {
71+
reject(error);
72+
},
73+
onError(error: unknown) {
74+
responseStatusCode = 500;
75+
// Log streaming rendering errors from inside the shell. Don't log
76+
// errors encountered during initial shell rendering since they'll
77+
// reject and get logged in handleDocumentRequest.
78+
if (shellRendered) {
79+
console.error(error);
80+
}
81+
},
82+
},
83+
);
84+
85+
setTimeout(abort, ABORT_DELAY);
86+
});
87+
}
88+
89+
function handleBrowserRequest(
90+
request: Request,
91+
responseStatusCode: number,
92+
responseHeaders: Headers,
93+
remixContext: EntryContext,
94+
) {
95+
return new Promise((resolve, reject) => {
96+
let shellRendered = false;
97+
const { pipe, abort } = renderToPipeableStream(
98+
<RemixServer context={remixContext} url={request.url} abortDelay={ABORT_DELAY} />,
99+
{
100+
onShellReady() {
101+
shellRendered = true;
102+
const body = new PassThrough();
103+
const stream = createReadableStreamFromReadable(body);
104+
105+
responseHeaders.set('Content-Type', 'text/html');
106+
107+
resolve(
108+
new Response(stream, {
109+
headers: responseHeaders,
110+
status: responseStatusCode,
111+
}),
112+
);
113+
114+
pipe(body);
115+
},
116+
onShellError(error: unknown) {
117+
reject(error);
118+
},
119+
onError(error: unknown) {
120+
responseStatusCode = 500;
121+
// Log streaming rendering errors from inside the shell. Don't log
122+
// errors encountered during initial shell rendering since they'll
123+
// reject and get logged in handleDocumentRequest.
124+
if (shellRendered) {
125+
console.error(error);
126+
}
127+
},
128+
},
129+
);
130+
131+
setTimeout(abort, ABORT_DELAY);
132+
});
133+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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 function ErrorBoundary() {
26+
const error = useRouteError();
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);
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)