Skip to content

Commit 99133e5

Browse files
committed
chore: add remix-fastify-vite test app
1 parent 7420db4 commit 99133e5

File tree

14 files changed

+618
-0
lines changed

14 files changed

+618
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/**
2+
* This is intended to be a basic starting point for linting in your app.
3+
* It relies on recommended configs out of the box for simplicity, but you can
4+
* and should modify this configuration to best suit your team's needs.
5+
*/
6+
7+
/** @type {import('eslint').Linter.Config} */
8+
module.exports = {
9+
root: true,
10+
parserOptions: {
11+
ecmaVersion: "latest",
12+
sourceType: "module",
13+
ecmaFeatures: {
14+
jsx: true,
15+
},
16+
},
17+
env: {
18+
browser: true,
19+
commonjs: true,
20+
es6: true,
21+
},
22+
ignorePatterns: ["!**/.server", "!**/.client"],
23+
24+
// Base config
25+
extends: ["eslint:recommended"],
26+
27+
overrides: [
28+
// React
29+
{
30+
files: ["**/*.{js,jsx,ts,tsx}"],
31+
plugins: ["react", "jsx-a11y"],
32+
extends: [
33+
"plugin:react/recommended",
34+
"plugin:react/jsx-runtime",
35+
"plugin:react-hooks/recommended",
36+
"plugin:jsx-a11y/recommended",
37+
],
38+
settings: {
39+
react: {
40+
version: "detect",
41+
},
42+
formComponents: ["Form"],
43+
linkComponents: [
44+
{ name: "Link", linkAttribute: "to" },
45+
{ name: "NavLink", linkAttribute: "to" },
46+
],
47+
"import/resolver": {
48+
typescript: {},
49+
},
50+
},
51+
},
52+
53+
// Typescript
54+
{
55+
files: ["**/*.{ts,tsx}"],
56+
plugins: ["@typescript-eslint", "import"],
57+
parser: "@typescript-eslint/parser",
58+
settings: {
59+
"import/internal-regex": "^~/",
60+
"import/resolver": {
61+
node: {
62+
extensions: [".ts", ".tsx"],
63+
},
64+
typescript: {
65+
alwaysTryTypes: true,
66+
},
67+
},
68+
},
69+
extends: [
70+
"plugin:@typescript-eslint/recommended",
71+
"plugin:import/recommended",
72+
"plugin:import/typescript",
73+
],
74+
},
75+
76+
// Node
77+
{
78+
files: [".eslintrc.cjs", "server.js"],
79+
env: {
80+
node: true,
81+
},
82+
},
83+
],
84+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules
2+
3+
/.cache
4+
/build
5+
.env
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Welcome to Remix + Vite!
2+
3+
📖 See the [Remix docs](https://remix.run/docs) and the [Remix Vite docs](https://remix.run/docs/en/main/future/vite) for details on supported features.
4+
5+
## Development
6+
7+
Run the Express server with Vite dev middleware:
8+
9+
```shellscript
10+
npm run dev
11+
```
12+
13+
## Deployment
14+
15+
First, build your app for production:
16+
17+
```sh
18+
npm run build
19+
```
20+
21+
Then run the app in production mode:
22+
23+
```sh
24+
npm start
25+
```
26+
27+
Now you'll need to pick a host to deploy it to.
28+
29+
### DIY
30+
31+
If you're familiar with deploying Express applications you should be right at home. Just make sure to deploy the output of `npm run build`
32+
33+
- `build/server`
34+
- `build/client`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { RemixBrowser, useLocation, useMatches } from '@remix-run/react';
2+
import * as Sentry from '@sentry/remix';
3+
import { StrictMode, startTransition, useEffect } from 'react';
4+
import { hydrateRoot } from 'react-dom/client';
5+
6+
Sentry.init({
7+
environment: 'qa', // dynamic sampling bias to keep transactions
8+
dsn: window.ENV.SENTRY_DSN,
9+
integrations: [
10+
Sentry.browserTracingIntegration({
11+
useEffect,
12+
useLocation,
13+
useMatches,
14+
}),
15+
Sentry.replayIntegration(),
16+
],
17+
// Performance Monitoring
18+
tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production!
19+
// Session Replay
20+
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.
21+
replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur.
22+
});
23+
24+
Sentry.addEventProcessor(event => {
25+
if (
26+
event.type === 'transaction' &&
27+
(event.contexts?.trace?.op === 'pageload' || event.contexts?.trace?.op === 'navigation')
28+
) {
29+
const eventId = event.event_id;
30+
if (eventId) {
31+
window.recordedTransactions = window.recordedTransactions || [];
32+
window.recordedTransactions.push(eventId);
33+
}
34+
}
35+
36+
return event;
37+
});
38+
39+
startTransition(() => {
40+
hydrateRoot(
41+
document,
42+
<StrictMode>
43+
<RemixBrowser />
44+
</StrictMode>,
45+
);
46+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import * as Sentry from '@sentry/remix';
2+
3+
Sentry.init({
4+
tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production!
5+
environment: 'qa', // dynamic sampling bias to keep transactions
6+
dsn: process.env.E2E_TEST_DSN,
7+
});
8+
9+
import { PassThrough } from 'node:stream';
10+
11+
import type { AppLoadContext, EntryContext } from '@remix-run/node';
12+
import { createReadableStreamFromReadable } from '@remix-run/node';
13+
import { RemixServer } from '@remix-run/react';
14+
import { isbot } from 'isbot';
15+
import { renderToPipeableStream } from 'react-dom/server';
16+
17+
const ABORT_DELAY = 5_000;
18+
19+
export default function handleRequest(
20+
request: Request,
21+
responseStatusCode: number,
22+
responseHeaders: Headers,
23+
remixContext: EntryContext,
24+
// This is ignored so we can keep it in the template for visibility. Feel
25+
// free to delete this parameter in your app if you're not using it!
26+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
27+
loadContext: AppLoadContext,
28+
) {
29+
return isbot(request.headers.get('user-agent') || '')
30+
? handleBotRequest(request, responseStatusCode, responseHeaders, remixContext)
31+
: handleBrowserRequest(request, responseStatusCode, responseHeaders, remixContext);
32+
}
33+
34+
function handleBotRequest(
35+
request: Request,
36+
responseStatusCode: number,
37+
responseHeaders: Headers,
38+
remixContext: EntryContext,
39+
) {
40+
return new Promise((resolve, reject) => {
41+
let shellRendered = false;
42+
const { pipe, abort } = renderToPipeableStream(
43+
<RemixServer context={remixContext} url={request.url} abortDelay={ABORT_DELAY} />,
44+
{
45+
onAllReady() {
46+
shellRendered = true;
47+
const body = new PassThrough();
48+
const stream = createReadableStreamFromReadable(body);
49+
50+
responseHeaders.set('Content-Type', 'text/html');
51+
52+
resolve(
53+
new Response(stream, {
54+
headers: responseHeaders,
55+
status: responseStatusCode,
56+
}),
57+
);
58+
59+
pipe(body);
60+
},
61+
onShellError(error: unknown) {
62+
reject(error);
63+
},
64+
onError(error: unknown) {
65+
responseStatusCode = 500;
66+
// Log streaming rendering errors from inside the shell. Don't log
67+
// errors encountered during initial shell rendering since they'll
68+
// reject and get logged in handleDocumentRequest.
69+
if (shellRendered) {
70+
console.error(error);
71+
}
72+
},
73+
},
74+
);
75+
76+
setTimeout(abort, ABORT_DELAY);
77+
});
78+
}
79+
80+
function handleBrowserRequest(
81+
request: Request,
82+
responseStatusCode: number,
83+
responseHeaders: Headers,
84+
remixContext: EntryContext,
85+
) {
86+
return new Promise((resolve, reject) => {
87+
let shellRendered = false;
88+
const { pipe, abort } = renderToPipeableStream(
89+
<RemixServer context={remixContext} url={request.url} abortDelay={ABORT_DELAY} />,
90+
{
91+
onShellReady() {
92+
shellRendered = true;
93+
const body = new PassThrough();
94+
const stream = createReadableStreamFromReadable(body);
95+
96+
responseHeaders.set('Content-Type', 'text/html');
97+
98+
resolve(
99+
new Response(stream, {
100+
headers: responseHeaders,
101+
status: responseStatusCode,
102+
}),
103+
);
104+
105+
pipe(body);
106+
},
107+
onShellError(error: unknown) {
108+
reject(error);
109+
},
110+
onError(error: unknown) {
111+
responseStatusCode = 500;
112+
// Log streaming rendering errors from inside the shell. Don't log
113+
// errors encountered during initial shell rendering since they'll
114+
// reject and get logged in handleDocumentRequest.
115+
if (shellRendered) {
116+
console.error(error);
117+
}
118+
},
119+
},
120+
);
121+
122+
setTimeout(abort, ABORT_DELAY);
123+
});
124+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Links, Meta, Outlet, Scripts, ScrollRestoration } from '@remix-run/react';
2+
3+
import { captureRemixErrorBoundaryError, withSentry } from '@sentry/remix';
4+
5+
export function Layout({ children }: { children: React.ReactNode }) {
6+
return (
7+
<html lang="en">
8+
<head>
9+
<meta charSet="utf-8" />
10+
<meta name="viewport" content="width=device-width, initial-scale=1" />
11+
<Meta />
12+
<Links />
13+
</head>
14+
<body>
15+
{children}
16+
<ScrollRestoration />
17+
<Scripts />
18+
</body>
19+
</html>
20+
);
21+
}
22+
23+
function App() {
24+
return <Outlet />;
25+
}
26+
27+
export default withSentry(App);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import type { MetaFunction } from '@remix-run/node';
2+
import * as Sentry from '@sentry/remix';
3+
4+
export const meta: MetaFunction = () => {
5+
return [{ title: 'New Remix App' }, { name: 'description', content: 'Welcome to Remix!' }];
6+
};
7+
8+
export default function Index() {
9+
return (
10+
<div style={{ fontFamily: 'system-ui, sans-serif', lineHeight: '1.8' }}>
11+
<h1>Welcome to Remix</h1>
12+
<ul>
13+
<li>
14+
<a target="_blank" href="https://remix.run/tutorials/blog" rel="noreferrer">
15+
15m Quickstart Blog Tutorial
16+
</a>
17+
</li>
18+
<li>
19+
<a target="_blank" href="https://remix.run/tutorials/jokes" rel="noreferrer">
20+
Deep Dive Jokes App Tutorial
21+
</a>
22+
</li>
23+
<li>
24+
<a target="_blank" href="https://remix.run/docs" rel="noreferrer">
25+
Remix Docs
26+
</a>
27+
</li>
28+
<li>
29+
<div>
30+
<span>Remix + Sentry on the client</span>
31+
<input
32+
type="button"
33+
value="Capture Exception"
34+
id="exception-button"
35+
onClick={() => {
36+
const eventId = Sentry.captureException(new Error('I am an error!'));
37+
window.capturedExceptionId = eventId;
38+
}}
39+
/>
40+
</div>
41+
</li>
42+
</ul>
43+
</div>
44+
);
45+
}

0 commit comments

Comments
 (0)