├── .gitignore
├── .npmrc
├── README.md
├── app
├── entry.server.jsx
├── movie-link.jsx
├── root.jsx
└── routes
│ ├── _index.jsx
│ ├── all-movies[.json].js
│ ├── movie.$id.jsx
│ └── search.jsx
├── migrations
├── 0001_schema.sql
├── 0002_movies.sql
└── 0003_fts_movies.sql
├── package-lock.json
├── package.json
├── public
├── _headers
├── _routes.json
└── favicon.ico
├── remix.config.js
├── server.ts
└── wrangler.example.toml
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
3 | /.cache
4 | /functions/\[\[path\]\].js
5 | /functions/\[\[path\]\].js.map
6 | /functions/metafile.*
7 | /functions/version.txt
8 | /public/build
9 | .dev.vars
10 | /.wrangler
11 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | legacy-peer-deps=true
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Remix Movies Example
2 |
3 | This movie database serves as an example for various data loading strategies in Remix.
4 |
5 | [Watch the "Remix Singles" on YouTube](https://www.youtube.com/playlist?list=PLXoynULbYuEApkwAGZ7U7LmL-BDHloB0l)
6 |
7 | ## Local Development
8 |
9 | To get started create a `wrangler.toml` configuration file, which will provide information like our database name.
10 |
11 | ```sh
12 | cp wrangler.example.toml wrangler.toml
13 | ```
14 |
15 | You'll notice that the `name` and `database_name` are already filled in. [Deploying to Cloudflare](#deploying-to-cloudflare) shows you how to create a Pages project and D1 database, which you can name these whatever you want.
16 |
17 | To create and seed your local database, run the following. There is a lot of data, so this may take a little while.
18 |
19 | ```sh
20 | npm run db:migrate -- --local
21 | ```
22 |
23 | You can now start the app locally.
24 |
25 | ```sh
26 | npm run dev
27 | ```
28 |
29 | ## Deploying to Cloudflare
30 |
31 | ### Create/log in to your Cloudflare account
32 |
33 | The deployed version of this application uses [Cloudflare Pages](https://developers.cloudflare.com/pages/) and [Cloudflare D1](https://developers.cloudflare.com/d1/). In order to create and use these resources, you'll first need to log in to your Cloudflare account.
34 |
35 | If you don't have an account this command will take you to a page where you can create one for free.
36 |
37 | ```sh
38 | npx wrangler login
39 | ```
40 |
41 | ### Create a D1 database
42 |
43 | First we need to provision a new D1 database. Be sure to copy the `database_id` returned and add it to your `wrangler.toml`.
44 |
45 | ```sh
46 | npx wrangler d1 create remix-movies-db
47 | ```
48 |
49 | To setup and seed the database, run the following
50 |
51 | > [!WARNING]
52 | > D1 is still in public beta (as of the making of this example app), and we're trying to seed
53 | > _a lot_ of data, so this migration may fail! You might have to try running it a few times.
54 |
55 | ```sh
56 | npm run db:migrate
57 | ```
58 |
59 | ### Create a Cloudflare Pages project
60 |
61 | Run the following to setup a new Cloudflare Pages project
62 |
63 | ```sh
64 | npx wrangler pages project create remix-movies
65 | ```
66 |
67 | Before you deploy your application, you'll need to bind your D1 database to your Pages Function.
68 |
69 | Select your Pages project > **Settings > Functions > D1 database bindings > Add binding**.
70 |
71 | Name the **Variable name** "DB" and select `remix-movies-db` (or whatever you named your D1 database) from the dropdown and hit **Save**.
72 |
73 | > [!NOTE]
74 | > If you are deploying a preview (i.e. on a non-`main` branch) you'll need to setup a D1 binding under the **Preview** tab
75 |
76 | If you get stuck, visit the [Cloudflare docs for the full instructions](https://developers.cloudflare.com/pages/functions/bindings/#d1-databases)
77 |
78 | Now you're ready to deploy your site!
79 |
80 | ```sh
81 | npm run pages:deploy
82 | ```
83 |
--------------------------------------------------------------------------------
/app/entry.server.jsx:
--------------------------------------------------------------------------------
1 | // Added this custom entry.server.jsx file to add the server timing header
2 | import { RemixServer } from '@remix-run/react'
3 | import isbot from 'isbot'
4 | import { renderToReadableStream } from 'react-dom/server'
5 |
6 | const ABORT_DELAY = 5000
7 |
8 | const handleRequest = async (
9 | request,
10 | responseStatusCode,
11 | responseHeaders,
12 | remixContext,
13 | ) => {
14 | let didError = false
15 |
16 | let start = Date.now()
17 | const stream = await renderToReadableStream(
18 | ,
23 | {
24 | onError: (error) => {
25 | console.log('Caught an error')
26 | didError = true
27 | console.error(error)
28 |
29 | // You can also log crash/error report
30 | },
31 | signal: AbortSignal.timeout(ABORT_DELAY),
32 | },
33 | )
34 |
35 | if (isbot(request.headers.get('user-agent'))) {
36 | await stream.allReady
37 | }
38 |
39 | let time = Date.now() - start
40 | responseHeaders.append('Server-Timing', `shell-render;dur=${time}`)
41 | responseHeaders.set('Transfer-Encoding', 'chunked')
42 | responseHeaders.set('Content-Type', 'text/html')
43 |
44 | return new Response(stream, {
45 | headers: responseHeaders,
46 | status: didError ? 500 : responseStatusCode,
47 | })
48 | }
49 |
50 | export default handleRequest
51 |
--------------------------------------------------------------------------------
/app/movie-link.jsx:
--------------------------------------------------------------------------------
1 | import { Link } from '@remix-run/react'
2 | import { useEffect, useState } from 'react'
3 |
4 | export function MovieLink({ movie }) {
5 | let [prefetch, setPrefetch] = useState('intent')
6 |
7 | // Don't prefetch cached movies
8 | useEffect(() => {
9 | if (sessionStorage.getItem(`movie-${movie.id}`)) {
10 | setPrefetch('none')
11 | }
12 | })
13 |
14 | let prefetchImage = () => {
15 | if (prefetch === 'none') return
16 | let img = new Image()
17 | img.src = movie.thumbnail
18 | }
19 |
20 | return (
21 |
27 | {movie.title}
28 |
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/app/root.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | Links,
3 | LiveReload,
4 | Meta,
5 | Outlet,
6 | Scripts,
7 | ScrollRestoration,
8 | } from '@remix-run/react'
9 | import { Search } from './routes/search'
10 |
11 | export default function App() {
12 | return (
13 |
14 |