Skip to content

Commit 103adca

Browse files
authored
Merge pull request #687 from jeffsee55/main
Update build script to use `remix vite:build` and update Readme with Vercel hosting instructions for Remix with Vite
1 parent c22a83b commit 103adca

17 files changed

+908
-7
lines changed

.graphqlrc.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import fs from "fs";
2+
import { LATEST_API_VERSION } from "@shopify/shopify-api";
3+
import { shopifyApiProject, ApiType } from "@shopify/api-codegen-preset";
4+
import type { IGraphQLConfig } from "graphql-config";
5+
6+
function getConfig() {
7+
const config: IGraphQLConfig = {
8+
projects: {
9+
default: shopifyApiProject({
10+
apiType: ApiType.Admin,
11+
apiVersion: LATEST_API_VERSION,
12+
documents: ["./app/**/*.{js,ts,jsx,tsx}"],
13+
outputDir: "./app/types",
14+
}),
15+
},
16+
};
17+
18+
let extensions: string[] = [];
19+
try {
20+
extensions = fs.readdirSync("./extensions");
21+
} catch {
22+
// ignore if no extensions
23+
}
24+
25+
for (const entry of extensions) {
26+
const extensionPath = `./extensions/${entry}`;
27+
const schema = `${extensionPath}/schema.graphql`;
28+
if (!fs.existsSync(schema)) {
29+
continue;
30+
}
31+
config.projects[entry] = {
32+
schema,
33+
documents: [`${extensionPath}/**/*.graphql`],
34+
};
35+
}
36+
37+
return config;
38+
}
39+
40+
module.exports = getConfig();

README.md

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -149,14 +149,26 @@ When you reach the step for [setting up environment variables](https://shopify.d
149149

150150
### Hosting on Vercel
151151

152-
When hosting your Shopify Remix app on Vercel, Vercel uses a fork of the [Remix library](https://github.com/vercel/remix).
153-
154-
To ensure all global variables are set correctly when you deploy your app to Vercel update your app to use the Vercel adapter instead of the node adapter.
152+
Using the Vercel Preset is recommended when hosting your Shopify Remix app on Vercel. You'll also want to ensure imports that would normally come from `@remix-run/node` are imported from `@vercel/remix` instead. Learn more about hosting Remix apps on Vercel [here](https://vercel.com/docs/frameworks/remix).
155153

156154
```diff
157-
// shopify.server.ts
158-
- import "@shopify/shopify-app-remix/adapters/node";
159-
+ import "@shopify/shopify-app-remix/adapters/vercel";
155+
// vite.config.ts
156+
import { vitePlugin as remix } from "@remix-run/dev";
157+
import { defineConfig, type UserConfig } from "vite";
158+
import tsconfigPaths from "vite-tsconfig-paths";
159+
+ import { vercelPreset } from '@vercel/remix/vite';
160+
161+
installGlobals();
162+
163+
export default defineConfig({
164+
plugins: [
165+
remix({
166+
ignoredRouteFiles: ["**/.*"],
167+
+ presets: [vercelPreset()],
168+
}),
169+
tsconfigPaths(),
170+
],
171+
});
160172
```
161173

162174
## Gotchas / Troubleshooting

app/db.server.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { PrismaClient } from "@prisma/client";
2+
3+
declare global {
4+
var prisma: PrismaClient;
5+
}
6+
7+
const prisma: PrismaClient = global.prisma || new PrismaClient();
8+
9+
if (process.env.NODE_ENV !== "production") {
10+
if (!global.prisma) {
11+
global.prisma = new PrismaClient();
12+
}
13+
}
14+
15+
export default prisma;

app/entry.server.tsx

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { PassThrough } from "stream";
2+
import { renderToPipeableStream } from "react-dom/server";
3+
import { RemixServer } from "@remix-run/react";
4+
import {
5+
createReadableStreamFromReadable,
6+
type EntryContext,
7+
} from "@remix-run/node";
8+
import { isbot } from "isbot";
9+
import { addDocumentResponseHeaders } from "./shopify.server";
10+
11+
const ABORT_DELAY = 5000;
12+
13+
export default async function handleRequest(
14+
request: Request,
15+
responseStatusCode: number,
16+
responseHeaders: Headers,
17+
remixContext: EntryContext
18+
) {
19+
addDocumentResponseHeaders(request, responseHeaders);
20+
const userAgent = request.headers.get("user-agent");
21+
const callbackName = isbot(userAgent ?? '')
22+
? "onAllReady"
23+
: "onShellReady";
24+
25+
return new Promise((resolve, reject) => {
26+
const { pipe, abort } = renderToPipeableStream(
27+
<RemixServer
28+
context={remixContext}
29+
url={request.url}
30+
abortDelay={ABORT_DELAY}
31+
/>,
32+
{
33+
[callbackName]: () => {
34+
const body = new PassThrough();
35+
const stream = createReadableStreamFromReadable(body);
36+
37+
responseHeaders.set("Content-Type", "text/html");
38+
resolve(
39+
new Response(stream, {
40+
headers: responseHeaders,
41+
status: responseStatusCode,
42+
})
43+
);
44+
pipe(body);
45+
},
46+
onShellError(error) {
47+
reject(error);
48+
},
49+
onError(error) {
50+
responseStatusCode = 500;
51+
console.error(error);
52+
},
53+
}
54+
);
55+
56+
setTimeout(abort, ABORT_DELAY);
57+
});
58+
}

app/globals.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
declare module "*.css";

app/root.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import {
2+
Links,
3+
Meta,
4+
Outlet,
5+
Scripts,
6+
ScrollRestoration,
7+
} from "@remix-run/react";
8+
9+
export default function App() {
10+
return (
11+
<html>
12+
<head>
13+
<meta charSet="utf-8" />
14+
<meta name="viewport" content="width=device-width,initial-scale=1" />
15+
<link rel="preconnect" href="https://cdn.shopify.com/" />
16+
<link
17+
rel="stylesheet"
18+
href="https://cdn.shopify.com/static/fonts/inter/v4/styles.css"
19+
/>
20+
<Meta />
21+
<Links />
22+
</head>
23+
<body>
24+
<Outlet />
25+
<ScrollRestoration />
26+
<Scripts />
27+
</body>
28+
</html>
29+
);
30+
}

app/routes/_index/route.tsx

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import type { LoaderFunctionArgs } from "@remix-run/node";
2+
import { json, redirect } from "@remix-run/node";
3+
import { Form, useLoaderData } from "@remix-run/react";
4+
5+
import { login } from "../../shopify.server";
6+
7+
import styles from "./styles.module.css";
8+
9+
export const loader = async ({ request }: LoaderFunctionArgs) => {
10+
const url = new URL(request.url);
11+
12+
if (url.searchParams.get("shop")) {
13+
throw redirect(`/app?${url.searchParams.toString()}`);
14+
}
15+
16+
return json({ showForm: Boolean(login) });
17+
};
18+
19+
export default function App() {
20+
const { showForm } = useLoaderData<typeof loader>();
21+
22+
return (
23+
<div className={styles.index}>
24+
<div className={styles.content}>
25+
<h1 className={styles.heading}>A short heading about [your app]</h1>
26+
<p className={styles.text}>
27+
A tagline about [your app] that describes your value proposition.
28+
</p>
29+
{showForm && (
30+
<Form className={styles.form} method="post" action="/auth/login">
31+
<label className={styles.label}>
32+
<span>Shop domain</span>
33+
<input className={styles.input} type="text" name="shop" />
34+
<span>e.g: my-shop-domain.myshopify.com</span>
35+
</label>
36+
<button className={styles.button} type="submit">
37+
Log in
38+
</button>
39+
</Form>
40+
)}
41+
<ul className={styles.list}>
42+
<li>
43+
<strong>Product feature</strong>. Some detail about your feature and
44+
its benefit to your customer.
45+
</li>
46+
<li>
47+
<strong>Product feature</strong>. Some detail about your feature and
48+
its benefit to your customer.
49+
</li>
50+
<li>
51+
<strong>Product feature</strong>. Some detail about your feature and
52+
its benefit to your customer.
53+
</li>
54+
</ul>
55+
</div>
56+
</div>
57+
);
58+
}

0 commit comments

Comments
 (0)