diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..91670c1
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,8 @@
+/.cache
+/.github
+/.husky
+/build
+/public
+/node_modules
+/dist
+/app/styles/tailwind.css
diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 0000000..4ffeeca
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,3 @@
+{
+ "extends": ["@remix-run", "prettier"]
+}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..4cd5de9
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,43 @@
+name: CI
+on:
+ - push
+jobs:
+ test:
+ name: 🔍 Testing
+ runs-on: ubuntu-latest
+ steps:
+ - name: ⬇️ Checkout repo
+ uses: actions/checkout@v3
+ - name: ⎔ Setup node
+ uses: actions/setup-node@v3
+ with:
+ node-version: 18
+ - name: 📥 Download deps
+ uses: bahmutov/npm-install@v1
+ with:
+ useLockFile: false
+ - name: 🎭 Install Playwright
+ run: npx playwright install --with-deps
+ - name: 📦 Prepare the environment
+ run: cp wrangler.toml.example wrangler.toml
+ - name: 💣 Run some tests
+ run: npx playwright test
+
+ lint:
+ name: ⬣ Linting
+ runs-on: ubuntu-latest
+ steps:
+ - name: ⬇️ Checkout repo
+ uses: actions/checkout@v3
+ - name: ⎔ Setup node
+ uses: actions/setup-node@v3
+ with:
+ node-version: 18
+ - name: 📥 Download deps
+ uses: bahmutov/npm-install@v1
+ with:
+ useLockFile: false
+ - name: ✨ Code format check
+ run: npx prettier --check .
+ - name: ✅ Code linting
+ run: npx eslint . --ext .js,.mjs,.ts,.tsx
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2565be0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,17 @@
+node_modules
+.env
+
+# Remix
+/.cache
+/build
+
+# Cloudflare
+.wrangler
+wrangler.toml
+
+# Playwright
+/test-results
+/playwright-report
+
+# Remix stacks
+/package-lock.json
\ No newline at end of file
diff --git a/.husky/.gitignore b/.husky/.gitignore
new file mode 100644
index 0000000..31354ec
--- /dev/null
+++ b/.husky/.gitignore
@@ -0,0 +1 @@
+_
diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100755
index 0000000..2312dc5
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1 @@
+npx lint-staged
diff --git a/.node-version b/.node-version
new file mode 100644
index 0000000..3c03207
--- /dev/null
+++ b/.node-version
@@ -0,0 +1 @@
+18
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..91670c1
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,8 @@
+/.cache
+/.github
+/.husky
+/build
+/public
+/node_modules
+/dist
+/app/styles/tailwind.css
diff --git a/.prettierrc.js b/.prettierrc.js
new file mode 100644
index 0000000..e554965
--- /dev/null
+++ b/.prettierrc.js
@@ -0,0 +1,32 @@
+/** @type {import("prettier").Config} */
+const config = {
+ arrowParens: 'avoid',
+ bracketSameLine: false,
+ bracketSpacing: true,
+ embeddedLanguageFormatting: 'auto',
+ endOfLine: 'lf',
+ htmlWhitespaceSensitivity: 'css',
+ insertPragma: false,
+ jsxSingleQuote: false,
+ printWidth: 80,
+ proseWrap: 'always',
+ quoteProps: 'as-needed',
+ requirePragma: false,
+ semi: true,
+ singleAttributePerLine: false,
+ singleQuote: true,
+ tabWidth: 2,
+ trailingComma: 'all',
+ useTabs: true,
+ overrides: [
+ {
+ files: ['**/*.json', '**/*.md'],
+ options: {
+ useTabs: false,
+ },
+ },
+ ],
+ plugins: ['prettier-plugin-tailwindcss'],
+};
+
+export default config;
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..db6f055
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 Edmund Hung
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..afd4cef
--- /dev/null
+++ b/README.md
@@ -0,0 +1,115 @@
+# remix-cloudflare-template
+
+Learn more about [Remix Stacks](https://remix.run/stacks).
+
+```
+npx create-remix@latest --template edmundhung/remix-cloudflare-template
+```
+
+What's included?
+
+- Development with [Vite](https://vitejs.dev)
+- [Github Actions](https://github.com/features/actions) for CI/CD
+- [Markdoc](https://markdoc.dev) for rendering markdown
+- Styling with [Tailwind](https://tailwindcss.com/)
+- End-to-end testing with [Playwright](playwright.dev/)
+- Local third party request mocking with [MSW](https://mswjs.io/)
+- Code formatting with [Prettier](https://prettier.io)
+- Linting with [ESLint](https://eslint.org)
+- Static Types with [TypeScript](https://typescriptlang.org)
+
+## Development
+
+Before start, copy `wrangler.toml.example` and name it `wrangler.toml`. This
+file will be used to configure the development environment and define all the
+environment variables that you need for your application.
+
+```sh
+cp wrangler.toml.example wrangler.toml
+```
+
+To starts the vite dev server:
+
+```sh
+npm run dev
+```
+
+You can also start the Playwright UI mode to test your application. You will
+find all the tests defined in the [/tests/e2e](./tests/e2e/) directory.
+
+```sh
+npm run test
+```
+
+To test your application on the workerd runtime, you can start the wrangler dev
+server with:
+
+```sh
+npm run build && npm run start
+```
+
+### New environment variable
+
+To add a new environment variable, you can update the **var** section on the
+[wrangler.toml](./wrangler.toml) file with the new variable:
+
+```toml
+[vars]
+NEW_VARIABLE = "..."
+```
+
+The variable will be available from the `env` object in the context.
+
+### Setup a KV Namespace
+
+To setup a new KV namespace on the **development environment**, update
+[wrangler.toml](./wrangler.toml) with another object similar to the cache
+namespace as shown below:
+
+```toml
+kv_namespaces = [
+ { binding = "cache", id = "cache" },
+ { binding = "new_namespace", id = "new_namespace" }
+]
+```
+
+Note that the `id` has no effect on the dev environment. You can use the same
+name for both `id` and `binding`. The namespace will be available form the `env`
+object in the context.
+
+### Improving types
+
+You can improve the types of the `env` object by updating
+[env.d.ts](./env.d.ts).
+
+## Deployment
+
+Before your first deployment, make sure all the environment variables and
+bindings are set properly on the
+[Cloudlfare Dashboard](https://dash.cloudflare.com/login).
+
+### Creating a new application
+
+To create a new application on the Cloudflare Dashboard, select **Workers and
+Pages** from the menu and click on **Create Application**. You can then follow
+the instructions based on your needs.
+
+### Setting up environment variables
+
+To set up environment variables, select **Workers and Pages** from the menu and
+look for the application details. You will find the **environment variables**
+section under the **Settings** tab.
+
+### Setting up KV namespaces
+
+To set up a new KV namespaces, you need to create a new namespace first through
+the **KV** menu under **Workers and Pages** and click **Create a namespace**.
+
+After creating the namespace, you can bind the namespace to the application from
+the application details page. You can find the setting from the **Functions**
+section under the **Settings** tab.
+
+### Debugging
+
+If your application is not working properly, you can find the real-time logs in
+the **Functions** tab from the deployment details page.
diff --git a/app/components.tsx b/app/components.tsx
new file mode 100644
index 0000000..3de3d41
--- /dev/null
+++ b/app/components.tsx
@@ -0,0 +1,30 @@
+import * as React from 'react';
+import markdoc, { type RenderableTreeNodes } from '@markdoc/markdoc';
+
+export function RemixLogo(props: React.ComponentPropsWithoutRef<'svg'>) {
+ return (
+
+ );
+}
+
+export function Markdown({ content }: { content: RenderableTreeNodes }) {
+ return