diff --git a/.eslintrc.js b/.eslintrc.js
index 2061cd2..0ef9764 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1,4 +1,4 @@
/** @type {import('eslint').Linter.Config} */
-module.exports = {
+export default {
extends: ["@remix-run/eslint-config", "@remix-run/eslint-config/node"],
};
diff --git a/app/entry.client.tsx b/app/entry.client.tsx
index f1fed31..999c0a1 100644
--- a/app/entry.client.tsx
+++ b/app/entry.client.tsx
@@ -2,21 +2,11 @@ import { RemixBrowser } from "@remix-run/react";
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";
-function hydrate() {
- startTransition(() => {
- hydrateRoot(
- document,
-
-
-
- );
- });
-}
-
-if (window.requestIdleCallback) {
- window.requestIdleCallback(hydrate);
-} else {
- // Safari doesn't support requestIdleCallback
- // https://caniuse.com/requestidlecallback
- window.setTimeout(hydrate, 1);
-}
+startTransition(() => {
+ hydrateRoot(
+ document,
+
+
+
+ );
+});
diff --git a/app/entry.server.tsx b/app/entry.server.tsx
index fb8ea87..beacf24 100644
--- a/app/entry.server.tsx
+++ b/app/entry.server.tsx
@@ -1,21 +1 @@
-import type { EntryContext } from "@remix-run/node";
-import { RemixServer } from "@remix-run/react";
-import { renderToString } from "react-dom/server";
-
-export default function handleRequest(
- request: Request,
- responseStatusCode: number,
- responseHeaders: Headers,
- remixContext: EntryContext
-) {
- const markup = renderToString(
-
- );
-
- responseHeaders.set("Content-Type", "text/html");
-
- return new Response("" + markup, {
- headers: responseHeaders,
- status: responseStatusCode,
- });
-}
+export { handleRequest as default } from "@netlify/remix-adapter";
diff --git a/package.json b/package.json
index 9b8ed36..225ba5a 100644
--- a/package.json
+++ b/package.json
@@ -1,15 +1,14 @@
{
"private": true,
"sideEffects": false,
+ "type": "module",
"scripts": {
"build": "remix build",
- "predev": "rimraf ./public/_redirects",
"dev": "remix dev",
"start": "netlify serve",
"typecheck": "tsc -b"
},
"dependencies": {
- "@netlify/functions": "^1.3.0",
"@remix-run/node": "*",
"@remix-run/react": "*",
"cross-env": "^7.0.3",
@@ -20,11 +19,10 @@
"@remix-run/dev": "*",
"@remix-run/eslint-config": "*",
"@remix-run/serve": "*",
- "@types/react": "^18.0.25",
- "@types/react-dom": "^18.0.8",
+ "@types/react": "^18.2.0",
+ "@types/react-dom": "^18.2.0",
"eslint": "^8.27.0",
- "rimraf": "^4.1.4",
- "typescript": "^5.1.0"
+ "typescript": "^5.2.2"
},
"engines": {
"node": ">=18"
diff --git a/remix.config.js b/remix.config.js
index 77dbc31..bbe7ebd 100644
--- a/remix.config.js
+++ b/remix.config.js
@@ -1,22 +1,10 @@
-const baseConfig =
- process.env.NODE_ENV === "production"
- ? // when running the Netify CLI or building on Netlify, we want to use
- {
- server: "./server.js",
- serverBuildPath: ".netlify/functions-internal/server.js",
- }
- : // otherwise support running remix dev, i.e. no custom server
- undefined;
+import { config } from "@netlify/remix-adapter";
/** @type {import('@remix-run/dev').AppConfig} */
-module.exports = {
- ...baseConfig,
- ignoredRouteFiles: ["**/.*"],
- // See https://remix.run/docs/en/main/file-conventions/route-files-v2
- future: {
- v2_routeConvention: true,
- }
+export default {
+ ...(process.env.NODE_ENV === "production" ? config : undefined),
+ // This works out of the box with the Netlify adapter, but you can
// add your own custom config here if you want to.
//
- // See https://remix.run/docs/en/v1/file-conventions/remix-config
+ // See https://remix.run/file-conventions/remix-config
};
diff --git a/remix.init/README-edge.md b/remix.init/README-edge.md
index 010d272..3c06738 100644
--- a/remix.init/README-edge.md
+++ b/remix.init/README-edge.md
@@ -40,26 +40,26 @@ npm install
Run
```sh
-netlify dev
+npm run dev
```
Open up [http://localhost:8888](http://localhost:8888), and you're ready to go!
### Serve your site locally
-Run
+To serve your site locally in a production-like environment, run
```sh
-npm netlify serve
+npm run start
```
-to serve your site locally at [http://localhost:8888](http://localhost:8888).
+Your site will be available at [http://localhost:8888](http://localhost:8888). Note that it will not auto-reload when you make changes.
## Excluding routes
You can exclude routes for non-Remix code such as custom Netlify Functions or Edge Functions. To do this, add an additional entry in the array like in the example below:
-````diff
+```diff
export const config = {
cache: "manual",
path: "/*",
@@ -70,6 +70,7 @@ export const config = {
- excluded_patterns: ["/_assets/*"],
+ excluded_patterns: ["/_assets/*", "/api/*"],
};
+```
## Deployment
@@ -81,4 +82,4 @@ netlify deploy --build
# production deployment
netlify deploy --build --prod
-````
+```
diff --git a/remix.init/README.md b/remix.init/README.md
index 7e2979b..52a6a52 100644
--- a/remix.init/README.md
+++ b/remix.init/README.md
@@ -43,21 +43,21 @@ Run
netlify dev
```
-Open up [http://localhost:8888](http://localhost:8888), and you're ready to go!
+Open up [http://localhost:3000](http://localhost:3000), and you're ready to go!
### Adding Redirects and Rewrites
-To add redirects and rewrites, add them to the `netlify.toml` file or to the [\_app_redirects](_app_redirects) file. For more information about redirects and rewrites, see the [Netlify docs](https://docs.netlify.com/routing/redirects/).
+To add redirects and rewrites, add them to the `netlify.toml` file. For more information about redirects and rewrites, see the [Netlify docs](https://docs.netlify.com/routing/redirects/).
### Serve your site locally
-Run
+To serve your site locally in a production-like environment, run
```sh
npm run start
```
-to serve your site locally at [http://localhost:8888](http://localhost:8888).
+Your site will be available at [http://localhost:8888](http://localhost:8888). Note that it will not auto-reload when you make changes.
## Deployment
diff --git a/remix.init/_app_redirects b/remix.init/_app_redirects
deleted file mode 100644
index cf91099..0000000
--- a/remix.init/_app_redirects
+++ /dev/null
@@ -1,7 +0,0 @@
-# This template uses this file instead of the typicial Netlify _redirects file.
-# For more information about redirects and rewrites, see https://docs.netlify.com/routing/redirects/.
-
-# Do not remove the line below. This is required to serve the site when deployed.
-/* /.netlify/functions/server 200
-
-# Add other redirects and rewrites here and/or in your netlify.toml
diff --git a/remix.init/entry.server.tsx b/remix.init/entry.server.tsx
index d6d0f0e..0ebf826 100644
--- a/remix.init/entry.server.tsx
+++ b/remix.init/entry.server.tsx
@@ -1,22 +1 @@
-import type { EntryContext } from "@remix-run/node";
-import { RemixServer } from "@remix-run/react";
-// Looking to use renderReadableStream? See https://github.com/netlify/remix-template/discussions/100
-import { renderToString } from "react-dom/server";
-
-export default function handleRequest(
- request: Request,
- responseStatusCode: number,
- responseHeaders: Headers,
- remixContext: EntryContext
-) {
- const markup = renderToString(
-
- );
-
- responseHeaders.set("Content-Type", "text/html");
-
- return new Response("" + markup, {
- headers: responseHeaders,
- status: responseStatusCode,
- });
-}
+export { handleRequest as default } from "@netlify/remix-edge-adapter";
diff --git a/remix.init/index.js b/remix.init/index.js
index 0a62acc..838aa22 100644
--- a/remix.init/index.js
+++ b/remix.init/index.js
@@ -3,15 +3,15 @@ const fs = require("fs/promises");
const { join } = require("path");
const PackageJson = require("@npmcli/package-json");
const execa = require("execa");
-const { Command } = require('commander');
+const { Command } = require("commander");
-const foldersToExclude = [".github", ".git"];
+const foldersToExclude = [".github"];
// Netlify Edge Functions template file changes
const edgeFilesToCopy = [
["README-edge.md", "README.md"],
["netlify-edge-toml", "netlify.toml"],
- ["server.js"],
+ ["server.ts"],
["remix.config.js"],
["entry.server.tsx", "app/entry.server.tsx"],
["root.tsx", "app/root.tsx"],
@@ -22,10 +22,9 @@ const edgeFilesToCopy = [
const filesToCopy = [
["README.md"],
["netlify-toml", "netlify.toml"],
- ["_app_redirects"],
+ ["redirects", ".redirects"],
];
-
async function copyTemplateFiles({ files, rootDirectory }) {
for (const [file, target] of files) {
let sourceFile = file;
@@ -41,10 +40,7 @@ async function copyTemplateFiles({ files, rootDirectory }) {
async function updatePackageJsonForEdge(directory) {
const packageJson = await PackageJson.load(directory);
const {
- dependencies: {
- "@remix-run/node": _node,
- ...dependencies
- },
+ dependencies: { "@remix-run/node": _node, ...dependencies },
scripts,
...restOfPackageJson
} = packageJson.content;
@@ -53,13 +49,14 @@ async function updatePackageJsonForEdge(directory) {
// dev script is the same as the start script for Netlify Edge, "cross-env NODE_ENV=production netlify dev"
scripts: {
...scripts,
- predev: "rimraf ./.netlify/edge-functions/",
+ dev: 'remix dev --manual -c "ntl dev --framework=#static"',
},
...restOfPackageJson,
dependencies: {
...dependencies,
"@netlify/edge-functions": "^2.0.0",
- "@netlify/remix-edge-adapter": "1.2.0",
+ "@netlify/remix-edge-adapter": "^3.0.0",
+ "@netlify/remix-runtime": "^2.0.0",
},
});
@@ -69,19 +66,25 @@ async function updatePackageJsonForEdge(directory) {
async function updatePackageJsonForFunctions(directory) {
const packageJson = await PackageJson.load(directory);
const {
- dependencies: {
- "@remix-run/node": _node,
- ...dependencies
- },
+ dependencies: { "@remix-run/node": _node, ...dependencies },
scripts,
...restOfPackageJson
} = packageJson.content;
packageJson.update({
...restOfPackageJson,
+ scripts: {
+ ...scripts,
+ build: "npm run redirects:enable && remix build",
+ dev: "npm run redirects:disable && remix dev",
+ "redirects:enable": "shx cp .redirects public/_redirects",
+ "redirects:disable": "shx rm -f public/_redirects",
+ },
dependencies: {
...dependencies,
- "@netlify/remix-adapter": "^1.0.0",
+ "@netlify/functions": "^2.0.0",
+ "@netlify/remix-adapter": "^2.0.0",
+ shx: "^0.3.4",
},
});
@@ -104,7 +107,24 @@ async function removeNonTemplateFiles({ rootDirectory, folders }) {
}
}
-async function main({ rootDirectory }) {
+async function installAdditionalDependencies({
+ rootDirectory,
+ packageManager,
+}) {
+ try {
+ console.log(`Installing additional dependencies with ${packageManager}.`);
+ const npmInstall = await execa(packageManager, ["install"], {
+ cwd: rootDirectory,
+ stdio: "inherit",
+ });
+ } catch (e) {
+ console.log(
+ `Unable to install additional packages. Run ${packageManager} install in the root of the new project, "${rootDirectory}".`
+ );
+ }
+}
+
+async function main({ rootDirectory, packageManager }) {
await removeNonTemplateFiles({
rootDirectory,
folders: foldersToExclude,
@@ -116,6 +136,7 @@ async function main({ rootDirectory }) {
rootDirectory,
});
await updatePackageJsonForFunctions(rootDirectory);
+ await installAdditionalDependencies({ rootDirectory, packageManager });
return;
}
@@ -126,30 +147,28 @@ async function main({ rootDirectory }) {
await updatePackageJsonForEdge(rootDirectory);
- // The Netlify Edge Functions template has different and additional dependencies to install.
- try {
- console.log("installing additional npm packages...");
- const npmInstall = await execa("npm", ["install"], { cwd: rootDirectory });
- console.log(npmInstall.stdout);
- } catch (e) {
- console.log(
- `Unable to install additional packages. Run npm install in the root of the new project, "${rootDirectory}".`
- );
- }
+ await installAdditionalDependencies({ rootDirectory, packageManager });
}
async function shouldUseEdge() {
-
// parse the top level command args to see if edge was passed in
const program = new Command();
program
- .option('--netlify-edge', 'explicitly use Netlify Edge Functions to serve this Remix site.', undefined)
- .option('--no-netlify-edge', 'explicitly do NOT use Netlify Edge Functions to serve this Remix site - use Serverless Functions instead.', undefined)
+ .option(
+ "--netlify-edge",
+ "explicitly use Netlify Edge Functions to serve this Remix site.",
+ undefined
+ )
+ .option(
+ "--no-netlify-edge",
+ "explicitly do NOT use Netlify Edge Functions to serve this Remix site - use Serverless Functions instead.",
+ undefined
+ );
program.allowUnknownOption().parse();
const passedEdgeOption = program.opts().netlifyEdge;
- if(passedEdgeOption !== true && passedEdgeOption !== false){
+ if (passedEdgeOption !== true && passedEdgeOption !== false) {
const { edge } = await inquirer.prompt([
{
name: "edge",
diff --git a/remix.init/netlify-edge-toml b/remix.init/netlify-edge-toml
index 13021f6..3525653 100644
--- a/remix.init/netlify-edge-toml
+++ b/remix.init/netlify-edge-toml
@@ -2,6 +2,11 @@
command = "npm run build"
publish = "public"
-[dev]
-command = "npm run dev"
-targetPort = 3000
+# Set immutable caching for static files, because they have fingerprinted filenames
+
+[[headers]]
+for = "/build/*"
+
+[headers.values]
+
+"Cache-Control" = "public, max-age=31560000, immutable"
diff --git a/remix.init/netlify-toml b/remix.init/netlify-toml
index 630bab8..e27db6b 100644
--- a/remix.init/netlify-toml
+++ b/remix.init/netlify-toml
@@ -1,14 +1,16 @@
[build]
-command = "remix build && cp _app_redirects public/_redirects"
+command = "npm run build"
publish = "public"
[dev]
command = "npm run dev"
targetPort = 3000
+# Set immutable caching for static files, because they have fingerprinted filenames
+
[[headers]]
for = "/build/*"
[headers.values]
-# Set to 60 seconds as an example. You can also add cache headers via Remix. See the documentation on [headers](https://remix.run/docs/en/v1/route/headers) in Remix.
-"Cache-Control" = "public, max-age=60, s-maxage=60"
+
+"Cache-Control" = "public, max-age=31560000, immutable"
diff --git a/remix.init/redirects b/remix.init/redirects
new file mode 100644
index 0000000..eb6d445
--- /dev/null
+++ b/remix.init/redirects
@@ -0,0 +1,4 @@
+# This file is copied into the public folder during the build step and removed during dev.
+# If you need to add your own redirects, add them in netlify.toml or they will be overwritten.
+# Do not remove the line below. This is required to serve the site when deployed.
+/* /.netlify/functions/server 200
\ No newline at end of file
diff --git a/remix.init/remix.config.js b/remix.init/remix.config.js
index 031471b..6113a42 100644
--- a/remix.init/remix.config.js
+++ b/remix.init/remix.config.js
@@ -1,16 +1,10 @@
-const { config } = require("@netlify/remix-edge-adapter");
-const baseConfig =
- process.env.NODE_ENV === "production"
- ? config
- : { ignoredRouteFiles: ["**/.*"], future: config.future };
+import { config } from '@netlify/remix-edge-adapter'
-/**
- * @type {import('@remix-run/dev').AppConfig}
- */
-module.exports = {
- ...baseConfig,
+/** @type {import('@remix-run/dev').AppConfig} */
+export default {
+ ...config,
// This works out of the box with the Netlify adapter, but you can
// add your own custom config here if you want to.
//
- // See https://remix.run/docs/en/v1/file-conventions/remix-config
-};
+ // See https://remix.run/file-conventions/remix-config
+}
diff --git a/remix.init/root.tsx b/remix.init/root.tsx
index 95772f0..d360df5 100644
--- a/remix.init/root.tsx
+++ b/remix.init/root.tsx
@@ -1,4 +1,5 @@
-import type { MetaFunction } from "@netlify/remix-runtime";
+import { cssBundleHref } from "@remix-run/css-bundle";
+import type { LinksFunction } from "@netlify/remix-runtime";
import {
Links,
LiveReload,
@@ -8,18 +9,16 @@ import {
ScrollRestoration,
} from "@remix-run/react";
-export const meta: MetaFunction = () => [
- {
- charset: "utf-8",
- title: "New Remix App",
- viewport: "width=device-width,initial-scale=1",
- }
+export const links: LinksFunction = () => [
+ ...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : []),
];
export default function App() {
return (
+
+
diff --git a/remix.init/server.js b/remix.init/server.ts
similarity index 69%
rename from remix.init/server.js
rename to remix.init/server.ts
index 18dc1b5..f73b344 100644
--- a/remix.init/server.js
+++ b/remix.init/server.ts
@@ -1,6 +1,6 @@
-// Import path interpreted by the Remix compiler
import * as build from "@remix-run/dev/server-build";
import { createRequestHandler } from "@netlify/remix-edge-adapter";
+import { broadcastDevReady } from "@netlify/remix-runtime";
export default createRequestHandler({
build,
@@ -8,6 +8,11 @@ export default createRequestHandler({
mode: process.env.NODE_ENV,
});
+if (process.env.NODE_ENV === "development") {
+ // Tell remix dev that the server is ready
+ broadcastDevReady(build);
+}
+
export const config = {
cache: "manual",
path: "/*",
@@ -15,5 +20,5 @@ export const config = {
//
// Add other exclusions here, e.g. "^/api/*$" for custom Netlify functions or
// custom Netlify Edge Functions
- excluded_patterns: ["^/_assets/*$"],
+ excludedPath: ["/build/*", "/favicon.ico"],
};
diff --git a/server.js b/server.ts
similarity index 71%
rename from server.js
rename to server.ts
index 6997ce3..d1d6d95 100644
--- a/server.js
+++ b/server.ts
@@ -1,7 +1,9 @@
-import { createRequestHandler } from "@netlify/remix-adapter";
import * as build from "@remix-run/dev/server-build";
+import { createRequestHandler } from "@netlify/remix-adapter";
-export const handler = createRequestHandler({
+const handler = createRequestHandler({
build,
mode: process.env.NODE_ENV,
});
+
+export default handler;
diff --git a/tsconfig.json b/tsconfig.json
index d8ee23a..2846d2f 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -3,11 +3,13 @@
"remix.env.d.ts",
"**/*.ts",
"**/*.tsx",
- "server.js",
- "remix.init/server.js"
],
"compilerOptions": {
- "lib": ["DOM", "DOM.Iterable", "ES2019"],
+ "lib": [
+ "DOM",
+ "DOM.Iterable",
+ "ES2019"
+ ],
"isolatedModules": true,
"esModuleInterop": true,
"jsx": "react-jsx",
@@ -20,10 +22,11 @@
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"paths": {
- "~/*": ["./app/*"]
+ "~/*": [
+ "./app/*"
+ ]
},
-
// Remix takes care of building everything in `remix build`.
"noEmit": true
}
-}
+}
\ No newline at end of file