diff --git a/.eslintignore b/.eslintignore
index 559d94f603b..9f26ea79d4c 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -11,3 +11,5 @@
integration/helpers/deno-template
packages/remix-deno
templates/deno
+
+packages/remix-dev/config/defaults
diff --git a/packages/remix-dev/compiler/compileBrowser.ts b/packages/remix-dev/compiler/compileBrowser.ts
index 338dd8a7dd8..0b556da807a 100644
--- a/packages/remix-dev/compiler/compileBrowser.ts
+++ b/packages/remix-dev/compiler/compileBrowser.ts
@@ -61,7 +61,7 @@ const createEsbuildConfig = (
options: CompileOptions
): esbuild.BuildOptions | esbuild.BuildIncremental => {
let entryPoints: esbuild.BuildOptions["entryPoints"] = {
- "entry.client": path.resolve(config.appDirectory, config.entryClientFile),
+ "entry.client": config.entryClientFile,
};
for (let id of Object.keys(config.routes)) {
// All route entry points are virtual modules that will be loaded by the
diff --git a/packages/remix-dev/compiler/plugins/serverEntryModulePlugin.ts b/packages/remix-dev/compiler/plugins/serverEntryModulePlugin.ts
index 56955b4ce4f..2e08c98bc9b 100644
--- a/packages/remix-dev/compiler/plugins/serverEntryModulePlugin.ts
+++ b/packages/remix-dev/compiler/plugins/serverEntryModulePlugin.ts
@@ -30,7 +30,7 @@ export function serverEntryModulePlugin(config: RemixConfig): Plugin {
resolveDir: config.appDirectory,
loader: "js",
contents: `
-import * as entryServer from ${JSON.stringify(`./${config.entryServerFile}`)};
+import * as entryServer from ${JSON.stringify(config.entryServerFile)};
${Object.keys(config.routes)
.map((key, index) => {
let route = config.routes[key];
diff --git a/packages/remix-dev/config.ts b/packages/remix-dev/config.ts
index 5c8a67ec29f..4aa2b80dbf7 100644
--- a/packages/remix-dev/config.ts
+++ b/packages/remix-dev/config.ts
@@ -353,15 +353,20 @@ export async function readConfig(
appConfig.cacheDirectory || ".cache"
);
- let entryClientFile = findEntry(appDirectory, "entry.client");
- if (!entryClientFile) {
- throw new Error(`Missing "entry.client" file in ${appDirectory}`);
- }
+ let defaultsDirectory = path.resolve(__dirname, "config", "defaults");
+ let defaultEntryClient = path.resolve(defaultsDirectory, "entry.client.tsx");
+ let defaultEntryServer = path.resolve(defaultsDirectory, "entry.server.tsx");
- let entryServerFile = findEntry(appDirectory, "entry.server");
- if (!entryServerFile) {
- throw new Error(`Missing "entry.server" file in ${appDirectory}`);
- }
+ let userEntryClientFile = findEntry(appDirectory, "entry.client");
+ let userEntryServerFile = findEntry(appDirectory, "entry.server");
+
+ let entryClientFile = userEntryClientFile
+ ? path.resolve(appDirectory, userEntryClientFile)
+ : defaultEntryClient;
+
+ let entryServerFile = userEntryServerFile
+ ? path.resolve(appDirectory, userEntryServerFile)
+ : defaultEntryServer;
let serverBuildPath = "build/index.js";
switch (serverBuildTarget) {
@@ -406,7 +411,7 @@ export async function readConfig(
Number(process.env.REMIX_DEV_SERVER_WS_PORT) ||
(await getPort({ port: Number(appConfig.devServerPort) || 8002 }));
// set env variable so un-bundled servers can use it
- process.env.REMIX_DEV_SERVER_WS_PORT = `${devServerPort}`;
+ process.env.REMIX_DEV_SERVER_WS_PORT = String(devServerPort);
let devServerBroadcastDelay = appConfig.devServerBroadcastDelay || 0;
let defaultPublicPath = "/build/";
@@ -419,12 +424,10 @@ export async function readConfig(
let publicPath = addTrailingSlash(appConfig.publicPath || defaultPublicPath);
let rootRouteFile = findEntry(appDirectory, "root");
- if (!rootRouteFile) {
- throw new Error(`Missing "root" route file in ${appDirectory}`);
- }
+ let defaultRootRouteFile = path.resolve(defaultsDirectory, "root.tsx");
let routes: RouteManifest = {
- root: { path: "", id: "root", file: rootRouteFile },
+ root: { path: "", id: "root", file: rootRouteFile || defaultRootRouteFile },
};
if (fse.existsSync(path.resolve(appDirectory, "routes"))) {
let conventionalRoutes = defineConventionalRoutes(
diff --git a/packages/remix-dev/config/defaults/entry.client.tsx b/packages/remix-dev/config/defaults/entry.client.tsx
new file mode 100644
index 00000000000..50fc10e785b
--- /dev/null
+++ b/packages/remix-dev/config/defaults/entry.client.tsx
@@ -0,0 +1,23 @@
+import * as React from "react";
+import { RemixBrowser } from "@remix-run/react";
+import { startTransition, StrictMode } from "react";
+import { hydrateRoot } from "react-dom/client";
+
+function hydrate() {
+ startTransition(() => {
+ hydrateRoot(
+ document,
+