diff --git a/examples/yarn-pnp/.eslintrc.js b/examples/yarn-pnp/.eslintrc.js
new file mode 100644
index 00000000000..ced78085f86
--- /dev/null
+++ b/examples/yarn-pnp/.eslintrc.js
@@ -0,0 +1,3 @@
+module.exports = {
+ extends: ["@remix-run/eslint-config", "@remix-run/eslint-config/node"],
+};
diff --git a/examples/yarn-pnp/.gitignore b/examples/yarn-pnp/.gitignore
new file mode 100644
index 00000000000..61ea1d3ccea
--- /dev/null
+++ b/examples/yarn-pnp/.gitignore
@@ -0,0 +1,19 @@
+node_modules
+
+/.cache
+/build
+/public/build
+/package-lock.json
+
+.yarn/*
+!.yarn/patches
+!.yarn/plugins
+!.yarn/releases
+!.yarn/sdks
+!.yarn/versions
+
+# Swap the comments on the following lines if you wish to use zero-installs
+# See https://yarnpkg.com/features/zero-installs and
+# https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored
+.pnp.*
+# !.yarn/cache
diff --git a/examples/yarn-pnp/README.md b/examples/yarn-pnp/README.md
new file mode 100644
index 00000000000..dec12bce575
--- /dev/null
+++ b/examples/yarn-pnp/README.md
@@ -0,0 +1,54 @@
+# Welcome to Remix!
+
+This is a very basic example of a Remix app, using the Remix App Server and Yarn PnP.
+
+- [Remix Docs](https://remix.run/docs)
+- [Yarn Plug'n'Play](https://next.yarnpkg.com/features/pnp)
+
+## Development
+
+From your terminal
+
+```sh
+yarn install
+yarn dev
+```
+
+This starts your app in development mode, rebuilding assets on file changes.
+
+## Deployment
+
+First, build your app for production:
+
+```sh
+yarn build
+```
+
+Then run the app in production mode:
+
+```sh
+yarn start
+```
+
+## Notes for Using Yarn PnP
+
+- You'll need to use Yarn ≥ v3.2.0. Older versions don't work because of [an issue with Yarn](https://github.com/yarnpkg/berry/issues/3687).
+- For editor support of PnP, refer to [Editor SDKs](https://yarnpkg.com/getting-started/editor-sdks).
+- When using TypeScript, consider installing the [Yarn TypeScript plugin](https://github.com/yarnpkg/berry/tree/master/packages/plugin-typescript).
+- For the `~/*` alias to work for imports relative to `app/*`, you have to add this to your `package.json`:
+ ```json
+ "dependencies": {
+ /* ... */
+ "~": "link:app/"
+ }
+ ```
+ You can then also remove the `~` alias from your `tsconfig.json`.
+- For only installing non-dev dependencies in production, you can use [`yarn workspaces focus`](https://yarnpkg.com/cli/workspaces/focus) after removing the `.yarn/cache` directory:
+ ```sh
+ yarn install
+ yarn build
+ rm -r .yarn/cache
+ yarn workspaces focus --production
+ yarn start
+ ```
+ Or check out [plugin-installs](https://gitlab.com/Larry1123/yarn-contrib/-/blob/master/packages/plugin-production-install/README.md) by [Larry1123](https://gitlab.com/Larry1123).
diff --git a/examples/yarn-pnp/app/entry.client.tsx b/examples/yarn-pnp/app/entry.client.tsx
new file mode 100644
index 00000000000..3eec1fd0a02
--- /dev/null
+++ b/examples/yarn-pnp/app/entry.client.tsx
@@ -0,0 +1,4 @@
+import { RemixBrowser } from "@remix-run/react";
+import { hydrate } from "react-dom";
+
+hydrate(, document);
diff --git a/examples/yarn-pnp/app/entry.server.tsx b/examples/yarn-pnp/app/entry.server.tsx
new file mode 100644
index 00000000000..5afa18235cc
--- /dev/null
+++ b/examples/yarn-pnp/app/entry.server.tsx
@@ -0,0 +1,21 @@
+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, {
+ status: responseStatusCode,
+ headers: responseHeaders,
+ });
+}
diff --git a/examples/yarn-pnp/app/root.tsx b/examples/yarn-pnp/app/root.tsx
new file mode 100644
index 00000000000..710d8e16118
--- /dev/null
+++ b/examples/yarn-pnp/app/root.tsx
@@ -0,0 +1,32 @@
+import type { MetaFunction } from "@remix-run/node";
+import {
+ Links,
+ LiveReload,
+ Meta,
+ Outlet,
+ Scripts,
+ ScrollRestoration,
+} from "@remix-run/react";
+
+export const meta: MetaFunction = () => ({
+ charset: "utf-8",
+ title: "New Remix App",
+ viewport: "width=device-width,initial-scale=1",
+});
+
+export default function App() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/examples/yarn-pnp/app/routes/index.tsx b/examples/yarn-pnp/app/routes/index.tsx
new file mode 100644
index 00000000000..da28b224ef3
--- /dev/null
+++ b/examples/yarn-pnp/app/routes/index.tsx
@@ -0,0 +1,18 @@
+export default function Index() {
+ return (
+
+
Welcome to Remix
+
+ This example is using{" "}
+
+ Yarn PnP
+
+ !
+
+
+ );
+}
diff --git a/examples/yarn-pnp/package.json b/examples/yarn-pnp/package.json
new file mode 100644
index 00000000000..4d749eebb51
--- /dev/null
+++ b/examples/yarn-pnp/package.json
@@ -0,0 +1,28 @@
+{
+ "name": "remix-example-yarn-pnp",
+ "private": true,
+ "sideEffects": false,
+ "scripts": {
+ "build": "remix build",
+ "dev": "remix dev",
+ "start": "remix-serve build"
+ },
+ "dependencies": {
+ "@remix-run/node": "1.5.1",
+ "@remix-run/react": "1.5.1",
+ "@remix-run/serve": "1.5.1",
+ "react": "^17.0.2",
+ "react-dom": "^17.0.2",
+ "~": "link:./app"
+ },
+ "devDependencies": {
+ "@remix-run/dev": "1.5.1",
+ "@types/react": "^17.0.24",
+ "@types/react-dom": "^17.0.9",
+ "typescript": "^4.1.2"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "packageManager": "yarn@3.2.1"
+}
diff --git a/examples/yarn-pnp/public/favicon.ico b/examples/yarn-pnp/public/favicon.ico
new file mode 100644
index 00000000000..8830cf6821b
Binary files /dev/null and b/examples/yarn-pnp/public/favicon.ico differ
diff --git a/examples/yarn-pnp/remix.config.js b/examples/yarn-pnp/remix.config.js
new file mode 100644
index 00000000000..260b82c7cb1
--- /dev/null
+++ b/examples/yarn-pnp/remix.config.js
@@ -0,0 +1,10 @@
+/**
+ * @type {import('@remix-run/dev').AppConfig}
+ */
+module.exports = {
+ ignoredRouteFiles: ["**/.*"],
+ // appDirectory: "app",
+ // assetsBuildDirectory: "public/build",
+ // serverBuildPath: "build/index.js",
+ // publicPath: "/build/",
+};
diff --git a/examples/yarn-pnp/remix.env.d.ts b/examples/yarn-pnp/remix.env.d.ts
new file mode 100644
index 00000000000..72e2affe311
--- /dev/null
+++ b/examples/yarn-pnp/remix.env.d.ts
@@ -0,0 +1,2 @@
+///
+///
diff --git a/examples/yarn-pnp/sandbox.config.json b/examples/yarn-pnp/sandbox.config.json
new file mode 100644
index 00000000000..4363d87a30d
--- /dev/null
+++ b/examples/yarn-pnp/sandbox.config.json
@@ -0,0 +1,6 @@
+{
+ "hardReloadOnChange": true,
+ "container": {
+ "port": 3000
+ }
+}
diff --git a/examples/yarn-pnp/tsconfig.json b/examples/yarn-pnp/tsconfig.json
new file mode 100644
index 00000000000..53f97889224
--- /dev/null
+++ b/examples/yarn-pnp/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"],
+ "compilerOptions": {
+ "lib": ["DOM", "DOM.Iterable", "ES2019"],
+ "isolatedModules": true,
+ "esModuleInterop": true,
+ "jsx": "react-jsx",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "target": "ES2019",
+ "strict": true,
+ "allowJs": true,
+ "forceConsistentCasingInFileNames": true,
+ "baseUrl": ".",
+
+ // Remix takes care of building everything in `remix build`.
+ "noEmit": true
+ }
+}