mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-06-26 18:26:38 +00:00
Migrate to vercel
This commit is contained in:
parent
f00dfa2f42
commit
3643b878b4
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
logs
|
||||
.vercel
|
||||
.cursor
|
||||
supabase/.temp
|
||||
*.log
|
||||
|
||||
12
api/index.js
Normal file
12
api/index.js
Normal file
@ -0,0 +1,12 @@
|
||||
import { createRequestHandler } from "@remix-run/vercel";
|
||||
import * as build from "../build/server/index.js";
|
||||
|
||||
export default createRequestHandler({
|
||||
build,
|
||||
getLoadContext(req) {
|
||||
return {
|
||||
env: process.env
|
||||
};
|
||||
},
|
||||
mode: process.env.NODE_ENV
|
||||
});
|
||||
42
app/api/[...path]/route.ts
Normal file
42
app/api/[...path]/route.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import * as Sentry from '@sentry/nextjs';
|
||||
import { createRequestHandler } from '~/lib/remix-types';
|
||||
// We'll import the server build at runtime, not during compilation
|
||||
// Build path will be available after the build is complete
|
||||
|
||||
// Add Sentry's request handler to wrap the Remix request handler
|
||||
const handleRequest = async (request: Request) => {
|
||||
try {
|
||||
// Dynamically import the server build at runtime
|
||||
// In a real Vercel deployment, the server build will be available
|
||||
// This is just a placeholder for type checking
|
||||
const build = { /* production build will be available at runtime */ };
|
||||
|
||||
// Create the request handler
|
||||
const handler = createRequestHandler({
|
||||
build: build as any,
|
||||
mode: process.env.NODE_ENV,
|
||||
getLoadContext: () => ({
|
||||
env: process.env
|
||||
})
|
||||
});
|
||||
|
||||
// Handle the request
|
||||
return handler(request);
|
||||
} catch (error) {
|
||||
// Log the error with Sentry
|
||||
Sentry.captureException(error);
|
||||
|
||||
// Return a basic error response
|
||||
return new Response('Server Error', { status: 500 });
|
||||
}
|
||||
};
|
||||
|
||||
export const GET = handleRequest;
|
||||
export const POST = handleRequest;
|
||||
export const PUT = handleRequest;
|
||||
export const PATCH = handleRequest;
|
||||
export const DELETE = handleRequest;
|
||||
export const HEAD = handleRequest;
|
||||
export const OPTIONS = handleRequest;
|
||||
|
||||
export const runtime = 'edge';
|
||||
@ -80,7 +80,10 @@ export const FileBreadcrumb = memo<FileBreadcrumbProps>(({ files, pathSegments =
|
||||
<DropdownMenu.Root open={isActive} modal={false}>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<span
|
||||
ref={(ref) => (segmentRefs.current[index] = ref)}
|
||||
ref={(ref) => {
|
||||
segmentRefs.current[index] = ref;
|
||||
return undefined;
|
||||
}}
|
||||
className={classNames('flex items-center gap-1.5 cursor-pointer shrink-0', {
|
||||
'text-bolt-elements-textTertiary hover:text-bolt-elements-textPrimary': !isActive,
|
||||
'text-bolt-elements-textPrimary underline': isActive,
|
||||
|
||||
@ -4,10 +4,10 @@ import { sentryHandleError } from '~/lib/sentry';
|
||||
* Using our conditional Sentry implementation instead of direct import
|
||||
* This avoids loading Sentry in development environments
|
||||
*/
|
||||
import type { AppLoadContext, EntryContext } from '@remix-run/cloudflare';
|
||||
import type { AppLoadContext, EntryContext } from '~/lib/remix-types';
|
||||
import { RemixServer } from '@remix-run/react';
|
||||
import { isbot } from 'isbot';
|
||||
import { renderToReadableStream } from 'react-dom/server';
|
||||
import { renderToString } from 'react-dom/server';
|
||||
import { renderHeadToString } from 'remix-island';
|
||||
import { Head } from './root';
|
||||
import { themeStore } from '~/lib/stores/theme';
|
||||
@ -18,71 +18,35 @@ export default async function handleRequest(
|
||||
request: Request,
|
||||
responseStatusCode: number,
|
||||
responseHeaders: Headers,
|
||||
remixContext: EntryContext,
|
||||
remixContext: any,
|
||||
_loadContext: AppLoadContext,
|
||||
) {
|
||||
// await initializeModelList({});
|
||||
// Check if the request is from a bot
|
||||
const userAgent = request.headers.get('user-agent');
|
||||
const isBot = isbot(userAgent || '');
|
||||
|
||||
const readable = await renderToReadableStream(<RemixServer context={remixContext} url={request.url} />, {
|
||||
signal: request.signal,
|
||||
onError(error: unknown) {
|
||||
console.error(error);
|
||||
responseStatusCode = 500;
|
||||
},
|
||||
});
|
||||
// Create the HTML string
|
||||
const markup = renderToString(
|
||||
<RemixServer context={remixContext} url={request.url} />
|
||||
);
|
||||
|
||||
// @ts-ignore - Fix for incompatible EntryContext types between different remix versions
|
||||
const head = renderHeadToString({ request, remixContext, Head });
|
||||
|
||||
const body = new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(
|
||||
new Uint8Array(
|
||||
new TextEncoder().encode(
|
||||
`<!DOCTYPE html><html lang="en" data-theme="${themeStore.value}"><head>${head}</head><body><div id="root" class="w-full h-full">`,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const reader = readable.getReader();
|
||||
|
||||
function read() {
|
||||
reader
|
||||
.read()
|
||||
.then(({ done, value }) => {
|
||||
if (done) {
|
||||
controller.enqueue(new Uint8Array(new TextEncoder().encode('</div></body></html>')));
|
||||
controller.close();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
controller.enqueue(value);
|
||||
read();
|
||||
})
|
||||
.catch((error) => {
|
||||
controller.error(error);
|
||||
readable.cancel();
|
||||
});
|
||||
}
|
||||
read();
|
||||
},
|
||||
|
||||
cancel() {
|
||||
readable.cancel();
|
||||
},
|
||||
});
|
||||
|
||||
if (isbot(request.headers.get('user-agent') || '')) {
|
||||
await readable.allReady;
|
||||
}
|
||||
// Build full HTML response
|
||||
const html = `<!DOCTYPE html>
|
||||
<html lang="en" data-theme="${themeStore.value}">
|
||||
<head>${head}</head>
|
||||
<body>
|
||||
<div id="root" class="w-full h-full">${markup}</div>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
responseHeaders.set('Content-Type', 'text/html');
|
||||
|
||||
responseHeaders.set('Cross-Origin-Embedder-Policy', 'require-corp');
|
||||
responseHeaders.set('Cross-Origin-Opener-Policy', 'same-origin');
|
||||
|
||||
return new Response(body, {
|
||||
return new Response(html, {
|
||||
headers: responseHeaders,
|
||||
status: responseStatusCode,
|
||||
});
|
||||
|
||||
2
app/env.d.ts
vendored
2
app/env.d.ts
vendored
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* <reference types="@remix-run/cloudflare" />
|
||||
* <reference types="@remix-run/node" />
|
||||
* <reference types="vite/client" />
|
||||
*/
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
* In production: Uses the real OpenTelemetry implementation
|
||||
*/
|
||||
|
||||
import type { AppLoadContext } from '@remix-run/cloudflare';
|
||||
import type { AppLoadContext } from '@remix-run/node';
|
||||
|
||||
// Function to check if we're in development environment
|
||||
const isDevelopment = (): boolean => process.env.NODE_ENV === 'development';
|
||||
|
||||
@ -10,7 +10,7 @@ import type { SpanExporter, ReadableSpan } from '@opentelemetry/sdk-trace-base';
|
||||
import { ConsoleSpanExporter, SimpleSpanProcessor, BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
|
||||
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';
|
||||
|
||||
import type { AppLoadContext } from '@remix-run/cloudflare';
|
||||
import type { AppLoadContext } from '@remix-run/node';
|
||||
|
||||
// used to implement concurrencyLimit in the otlp exporter
|
||||
class Semaphore {
|
||||
@ -201,8 +201,8 @@ async function loadAsyncHooksContextManager() {
|
||||
}
|
||||
|
||||
export async function createTracer(appContext: AppLoadContext) {
|
||||
const honeycombApiKey = (appContext.cloudflare.env as any).HONEYCOMB_API_KEY;
|
||||
const honeycombDataset = (appContext.cloudflare.env as any).HONEYCOMB_DATASET;
|
||||
const honeycombApiKey = process.env.HONEYCOMB_API_KEY;
|
||||
const honeycombDataset = process.env.HONEYCOMB_DATASET;
|
||||
|
||||
if (!honeycombApiKey || !honeycombDataset) {
|
||||
console.warn('OpenTelemetry initialization skipped: HONEYCOMB_API_KEY and/or HONEYCOMB_DATASET not set');
|
||||
|
||||
47
app/lib/remix-types.ts
Normal file
47
app/lib/remix-types.ts
Normal file
@ -0,0 +1,47 @@
|
||||
// This file provides compatibility types to smoothly migrate from Cloudflare to Vercel
|
||||
|
||||
import type {
|
||||
ActionFunctionArgs as VercelActionFunctionArgs,
|
||||
LoaderFunctionArgs as VercelLoaderFunctionArgs,
|
||||
AppLoadContext as VercelAppLoadContext,
|
||||
EntryContext as VercelEntryContext
|
||||
} from '@vercel/remix';
|
||||
|
||||
// Re-export necessary types with compatible names
|
||||
export type ActionFunctionArgs = VercelActionFunctionArgs;
|
||||
export type LoaderFunctionArgs = VercelLoaderFunctionArgs;
|
||||
export type LoaderFunction = (args: LoaderFunctionArgs) => Promise<Response> | Response;
|
||||
export type ActionFunction = (args: ActionFunctionArgs) => Promise<Response> | Response;
|
||||
export type AppLoadContext = VercelAppLoadContext;
|
||||
export type EntryContext = VercelEntryContext;
|
||||
export type MetaFunction = () => Array<{
|
||||
title?: string;
|
||||
name?: string;
|
||||
content?: string;
|
||||
[key: string]: string | undefined;
|
||||
}>;
|
||||
export type LinksFunction = () => Array<{ rel: string; href: string }>;
|
||||
|
||||
// Re-export json function
|
||||
export function json<T>(data: T, init?: ResponseInit): Response {
|
||||
return new Response(JSON.stringify(data), {
|
||||
...init,
|
||||
headers: {
|
||||
...(init?.headers || {}),
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Export a createRequestHandler function
|
||||
export function createRequestHandler(options: {
|
||||
build: any;
|
||||
mode?: string;
|
||||
getLoadContext?: (req: Request) => AppLoadContext;
|
||||
}) {
|
||||
return async (request: Request) => {
|
||||
// This is a simplified handler for type checking
|
||||
// The real implementation will use Vercel's handler
|
||||
return new Response("Not implemented", { status: 501 });
|
||||
};
|
||||
}
|
||||
@ -24,7 +24,7 @@ export function sentryHandleError(error: Error): Error {
|
||||
* In production, dynamically import and use Sentry
|
||||
* This code only executes in production, so the import will never run in dev
|
||||
*/
|
||||
import('@sentry/remix')
|
||||
import('@sentry/nextjs')
|
||||
.then((sentry) => {
|
||||
sentry.captureException(error);
|
||||
})
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { sentryHandleError } from '~/lib/sentry';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import type { LinksFunction, LoaderFunction } from '@remix-run/cloudflare';
|
||||
import { json } from '@remix-run/cloudflare';
|
||||
import type { LinksFunction, LoaderFunction } from '~/lib/remix-types';
|
||||
import { json } from '~/lib/remix-types';
|
||||
import { Links, Meta, Outlet, Scripts, ScrollRestoration, useRouteError, useLoaderData } from '@remix-run/react';
|
||||
import tailwindReset from '@unocss/reset/tailwind-compat.css?url';
|
||||
import { themeStore } from './lib/stores/theme';
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { json, type MetaFunction } from '@remix-run/cloudflare';
|
||||
import { json, type MetaFunction } from '~/lib/remix-types';
|
||||
import { Suspense } from 'react';
|
||||
import { ClientOnly } from 'remix-utils/client-only';
|
||||
import { BaseChat } from '~/components/chat/BaseChat';
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { LoaderFunction } from '@remix-run/cloudflare';
|
||||
import type { LoaderFunction } from '~/lib/remix-types';
|
||||
import { providerBaseUrlEnvKeys } from '~/utils/constants';
|
||||
|
||||
export const loader: LoaderFunction = async ({ context, request }) => {
|
||||
@ -10,7 +10,8 @@ export const loader: LoaderFunction = async ({ context, request }) => {
|
||||
}
|
||||
|
||||
const envVarName = providerBaseUrlEnvKeys[provider].apiTokenKey;
|
||||
const isSet = !!(process.env[envVarName] || (context?.cloudflare?.env as Record<string, any>)?.[envVarName]);
|
||||
// Use only process.env since context.env might be undefined
|
||||
const isSet = !!process.env[envVarName];
|
||||
|
||||
return Response.json({ isSet });
|
||||
};
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { json } from '@remix-run/cloudflare';
|
||||
import type { ActionFunctionArgs, LoaderFunctionArgs } from '@remix-run/cloudflare';
|
||||
import { json } from '~/lib/remix-types';
|
||||
import type { ActionFunctionArgs, LoaderFunctionArgs } from '~/lib/remix-types';
|
||||
|
||||
// Handle all HTTP methods
|
||||
export async function action({ request, params }: ActionFunctionArgs) {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { LoaderFunctionArgs } from '@remix-run/cloudflare';
|
||||
import type { LoaderFunctionArgs } from '~/lib/remix-types';
|
||||
|
||||
export const loader = async ({ request: _request }: LoaderFunctionArgs) => {
|
||||
// Return a simple 200 OK response with some basic health information
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { json } from '@remix-run/cloudflare';
|
||||
import { json } from '~/lib/remix-types';
|
||||
import { MODEL_LIST } from '~/utils/constants';
|
||||
|
||||
export async function loader() {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { json, type ActionFunctionArgs } from '@remix-run/cloudflare';
|
||||
import { json, type ActionFunctionArgs } from '~/lib/remix-types';
|
||||
|
||||
async function pingTelemetry(event: string, data: any): Promise<boolean> {
|
||||
console.log('PingTelemetry', event, data);
|
||||
@ -29,10 +29,10 @@ export async function action(args: ActionFunctionArgs) {
|
||||
}
|
||||
|
||||
async function pingTelemetryAction({ request }: ActionFunctionArgs) {
|
||||
const { event, data } = await request.json<{
|
||||
const { event, data } = await request.json() as {
|
||||
event: string;
|
||||
data: any;
|
||||
}>();
|
||||
};
|
||||
|
||||
const success = await pingTelemetry(event, data);
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { json, type LoaderFunctionArgs } from '@remix-run/cloudflare';
|
||||
import { json, type LoaderFunctionArgs } from '~/lib/remix-types';
|
||||
import { default as IndexRoute } from './_index';
|
||||
|
||||
export async function loader(args: LoaderFunctionArgs) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { LoaderFunctionArgs } from '@remix-run/cloudflare';
|
||||
import { json, type MetaFunction } from '@remix-run/cloudflare';
|
||||
import type { LoaderFunctionArgs } from '~/lib/remix-types';
|
||||
import { json, type MetaFunction } from '~/lib/remix-types';
|
||||
import { ClientOnly } from 'remix-utils/client-only';
|
||||
import { BaseChat } from '~/components/chat/BaseChat';
|
||||
import { GitUrlImport } from '~/components/git/GitUrlImport.client';
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { json, type LoaderFunctionArgs } from '@remix-run/cloudflare';
|
||||
import { json, type LoaderFunctionArgs } from '~/lib/remix-types';
|
||||
import { default as IndexRoute } from './_index';
|
||||
|
||||
export async function loader(args: LoaderFunctionArgs) {
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
import type { ServerBuild } from '@remix-run/cloudflare';
|
||||
import { createPagesFunctionHandler } from '@remix-run/cloudflare-pages';
|
||||
|
||||
// @ts-ignore because the server build file is generated by `remix vite:build`
|
||||
import * as serverBuild from '../build/server';
|
||||
|
||||
export const onRequest = createPagesFunctionHandler({
|
||||
build: serverBuild as unknown as ServerBuild,
|
||||
});
|
||||
@ -1,10 +0,0 @@
|
||||
import * as Sentry from '@sentry/cloudflare';
|
||||
|
||||
export const onRequest = [
|
||||
// Make sure Sentry is the first middleware
|
||||
Sentry.sentryPagesPlugin((_context) => ({
|
||||
dsn: 'https://5465638ce4f73a256d861820b3a4dad4@o437061.ingest.us.sentry.io/4508853437399040',
|
||||
})),
|
||||
|
||||
// if we ever add more middleware, add them below:
|
||||
];
|
||||
@ -1,9 +1,6 @@
|
||||
import { type PlatformProxy } from 'wrangler';
|
||||
|
||||
type Cloudflare = Omit<PlatformProxy<Env>, 'dispose'>;
|
||||
|
||||
declare module '@remix-run/cloudflare' {
|
||||
// Vercel load context
|
||||
declare module '@remix-run/node' {
|
||||
interface AppLoadContext {
|
||||
cloudflare: Cloudflare;
|
||||
env: typeof process.env;
|
||||
}
|
||||
}
|
||||
|
||||
32
package.json
32
package.json
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "bolt",
|
||||
"name": "Nut",
|
||||
"description": "An AI Agent",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
@ -7,9 +7,9 @@
|
||||
"type": "module",
|
||||
"version": "0.0.5",
|
||||
"scripts": {
|
||||
"deploy": "npm run build && wrangler pages deploy",
|
||||
"build": "remix vite:build",
|
||||
"dev": "node pre-start.cjs && remix vite:dev",
|
||||
"deploy": "vercel deploy --prod",
|
||||
"build": "npx remix vite:build",
|
||||
"dev": "node pre-start.cjs && npx remix vite:dev",
|
||||
"test": "vitest --run --exclude tests/e2e",
|
||||
"test:watch": "vitest",
|
||||
"test:e2e": "playwright test",
|
||||
@ -18,17 +18,14 @@
|
||||
"test:e2e:legacy": "USE_SUPABASE=false playwright test",
|
||||
"lint": "eslint --cache --cache-location ./node_modules/.cache/eslint app",
|
||||
"lint:fix": "npm run lint -- --fix && prettier app --write",
|
||||
"start:windows": "wrangler pages dev ./build/client",
|
||||
"start:unix": "bindings=$(./bindings.sh) && wrangler pages dev ./build/client $bindings",
|
||||
"start": "node -e \"const { spawn } = require('child_process'); const isWindows = process.platform === 'win32'; const cmd = isWindows ? 'npm run start:windows' : 'npm run start:unix'; const child = spawn(cmd, { shell: true, stdio: 'inherit' }); child.on('exit', code => process.exit(code));\"",
|
||||
"dockerstart": "bindings=$(./bindings.sh) && wrangler pages dev ./build/client $bindings --ip 0.0.0.0 --port 5173 --no-show-interactive-dev-session",
|
||||
"start": "remix-serve ./build/server/index.mjs",
|
||||
"dockerrun": "docker run -it -d --name bolt-ai-live -p 5173:5173 --env-file .env.local bolt-ai",
|
||||
"dockerbuild:prod": "docker build -t bolt-ai:production -t bolt-ai:latest --target bolt-ai-production .",
|
||||
"dockerbuild": "docker build -t bolt-ai:development -t bolt-ai:latest --target bolt-ai-development .",
|
||||
"typecheck": "tsc",
|
||||
"typegen": "wrangler types",
|
||||
"preview": "pnpm run build && pnpm run start",
|
||||
"prepare": "husky"
|
||||
"prepare": "husky",
|
||||
"postinstall": "remix setup node"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.18.0"
|
||||
@ -60,7 +57,6 @@
|
||||
"@iconify-json/ph": "^1.2.1",
|
||||
"@iconify-json/svg-spinners": "^1.2.1",
|
||||
"@lezer/highlight": "^1.2.1",
|
||||
"@microlabs/otel-cf-workers": "1.0.0-rc.49",
|
||||
"@nanostores/react": "^0.7.3",
|
||||
"@octokit/rest": "^21.0.2",
|
||||
"@octokit/types": "^13.6.2",
|
||||
@ -79,15 +75,16 @@
|
||||
"@radix-ui/react-separator": "^1.1.0",
|
||||
"@radix-ui/react-switch": "^1.1.1",
|
||||
"@radix-ui/react-tooltip": "^1.1.4",
|
||||
"@remix-run/cloudflare": "^2.15.0",
|
||||
"@remix-run/cloudflare-pages": "^2.15.0",
|
||||
"@remix-run/node": "2.16.0",
|
||||
"@remix-run/react": "^2.15.0",
|
||||
"@sentry/cloudflare": "^8.55.0",
|
||||
"@remix-run/vercel": "1.19.3",
|
||||
"@sentry/nextjs": "9.5.0",
|
||||
"@sentry/remix": "^8",
|
||||
"@sentry/vite-plugin": "^3.1.2",
|
||||
"@supabase/supabase-js": "^2.49.1",
|
||||
"@uiw/codemirror-theme-vscode": "^4.23.6",
|
||||
"@unocss/reset": "^0.61.9",
|
||||
"@vercel/remix": "2.15.3",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/addon-web-links": "^0.11.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
@ -124,13 +121,14 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@blitz/eslint-plugin": "0.1.0",
|
||||
"@cloudflare/workers-types": "^4.20241127.0",
|
||||
"@playwright/test": "^1.51.0",
|
||||
"@remix-run/dev": "^2.15.0",
|
||||
"@remix-run/dev": "^2.15.3",
|
||||
"@remix-run/serve": "^2.16.0",
|
||||
"@types/diff": "^5.2.3",
|
||||
"@types/dom-speech-recognition": "^0.0.4",
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/node": "^20.0.0",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@types/react-modal": "^3.16.3",
|
||||
@ -144,12 +142,12 @@
|
||||
"typescript": "5.8.0-beta",
|
||||
"unified": "^11.0.5",
|
||||
"unocss": "^0.61.9",
|
||||
"vercel": "^41.4.1",
|
||||
"vite": "^5.4.11",
|
||||
"vite-plugin-node-polyfills": "^0.22.0",
|
||||
"vite-plugin-optimize-css-modules": "^1.1.0",
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
"vitest": "^2.1.7",
|
||||
"wrangler": "^3.91.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"resolutions": {
|
||||
|
||||
4246
pnpm-lock.yaml
4246
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
9
sentry.client.config.ts
Normal file
9
sentry.client.config.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import * as Sentry from '@sentry/nextjs';
|
||||
|
||||
Sentry.init({
|
||||
dsn: 'https://5465638ce4f73a256d861820b3a4dad4@o437061.ingest.us.sentry.io/4508853437399040',
|
||||
tracesSampleRate: 1.0,
|
||||
replaysSessionSampleRate: 0.1,
|
||||
replaysOnErrorSampleRate: 1.0,
|
||||
enabled: process.env.NODE_ENV === 'production',
|
||||
});
|
||||
7
sentry.server.config.ts
Normal file
7
sentry.server.config.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import * as Sentry from '@sentry/nextjs';
|
||||
|
||||
Sentry.init({
|
||||
dsn: 'https://5465638ce4f73a256d861820b3a4dad4@o437061.ingest.us.sentry.io/4508853437399040',
|
||||
tracesSampleRate: 1.0,
|
||||
enabled: process.env.NODE_ENV === 'production',
|
||||
});
|
||||
15
server.js
Normal file
15
server.js
Normal file
@ -0,0 +1,15 @@
|
||||
import { createRequestHandler } from "@remix-run/node";
|
||||
import * as build from "./build/server/index.js";
|
||||
|
||||
// This is the main server entry point for Vercel
|
||||
const handler = createRequestHandler({
|
||||
build,
|
||||
getLoadContext(req, res) {
|
||||
return {
|
||||
env: process.env
|
||||
};
|
||||
},
|
||||
mode: process.env.NODE_ENV
|
||||
});
|
||||
|
||||
export default handler;
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||
"types": ["@remix-run/cloudflare", "vite/client", "@cloudflare/workers-types/2023-07-01", "@types/dom-speech-recognition"],
|
||||
"types": ["@remix-run/node", "vite/client", "@types/node", "@types/dom-speech-recognition"],
|
||||
"isolatedModules": true,
|
||||
"esModuleInterop": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
22
vercel.json
Normal file
22
vercel.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"buildCommand": "npx remix vite:build",
|
||||
"framework": "remix",
|
||||
"installCommand": "pnpm install",
|
||||
"outputDirectory": "build",
|
||||
"regions": ["iad1"],
|
||||
"headers": [
|
||||
{
|
||||
"source": "/(.*)",
|
||||
"headers": [
|
||||
{
|
||||
"key": "Cross-Origin-Embedder-Policy",
|
||||
"value": "require-corp"
|
||||
},
|
||||
{
|
||||
"key": "Cross-Origin-Opener-Policy",
|
||||
"value": "same-origin"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
import { cloudflareDevProxyVitePlugin as remixCloudflareDevProxy, vitePlugin as remixVitePlugin } from '@remix-run/dev';
|
||||
import { vitePlugin as remixVitePlugin } from '@remix-run/dev';
|
||||
import { vercelPreset } from '@vercel/remix/vite';
|
||||
import UnoCSS from 'unocss/vite';
|
||||
import { defineConfig, type ViteDevServer } from 'vite';
|
||||
import { nodePolyfills } from 'vite-plugin-node-polyfills';
|
||||
@ -37,7 +38,6 @@ export default defineConfig((config) => {
|
||||
nodePolyfills({
|
||||
include: ['path', 'buffer', 'process'],
|
||||
}),
|
||||
config.mode !== 'test' && remixCloudflareDevProxy(),
|
||||
remixVitePlugin({
|
||||
future: {
|
||||
v3_fetcherPersist: true,
|
||||
@ -45,6 +45,7 @@ export default defineConfig((config) => {
|
||||
v3_throwAbortReason: true,
|
||||
v3_lazyRouteDiscovery: true
|
||||
},
|
||||
presets: [vercelPreset()],
|
||||
}),
|
||||
UnoCSS(),
|
||||
tsconfigPaths(),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user