├── .funcignore ├── .github └── workflows │ ├── azure-static-web-apps-ashy-grass-090f92903.yml │ └── azure-static-web-apps-yellow-sky-07aa3e70f.yml ├── .gitignore ├── .node-version ├── .npmrc ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── README.md ├── app ├── entry.client.tsx ├── entry.server.tsx ├── root.tsx ├── routes │ ├── 404.tsx │ ├── index.tsx │ └── page-2.tsx └── styles │ ├── global.css │ └── index.css ├── azure ├── function.json └── function │ ├── handler.js │ └── index.js ├── host.json ├── package-lock.json ├── package.json ├── proxies.json ├── public ├── favicon.png ├── index.html └── staticwebapp.config.json ├── remix.config.js ├── remix.env.d.ts └── tsconfig.json /.funcignore: -------------------------------------------------------------------------------- 1 | *.js.map 2 | *.ts 3 | .git* 4 | .vscode 5 | local.settings.json 6 | test 7 | tsconfig.json -------------------------------------------------------------------------------- /.github/workflows/azure-static-web-apps-ashy-grass-090f92903.yml: -------------------------------------------------------------------------------- 1 | name: Azure Static Web Apps CI/CD 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | types: [opened, synchronize, reopened, closed] 9 | branches: 10 | - main 11 | 12 | jobs: 13 | build_and_deploy_job: 14 | if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') 15 | runs-on: ubuntu-latest 16 | name: Build and Deploy Job 17 | steps: 18 | - uses: actions/checkout@v2 19 | with: 20 | submodules: true 21 | - name: Build And Deploy 22 | id: builddeploy 23 | uses: Azure/static-web-apps-deploy@v1 24 | with: 25 | azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_ASHY_GRASS_090F92903 }} 26 | repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments) 27 | action: "upload" 28 | ###### Repository/Build Configurations - These values can be configured to match your app requirements. ###### 29 | # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig 30 | app_location: "/" # App source code path 31 | api_location: "/" # Api source code path - optional 32 | output_location: "/public" # Built app content directory - optional 33 | ###### End of Repository/Build Configurations ###### 34 | 35 | close_pull_request_job: 36 | if: github.event_name == 'pull_request' && github.event.action == 'closed' 37 | runs-on: ubuntu-latest 38 | name: Close Pull Request Job 39 | steps: 40 | - name: Close Pull Request 41 | id: closepullrequest 42 | uses: Azure/static-web-apps-deploy@v1 43 | with: 44 | azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_ASHY_GRASS_090F92903 }} 45 | action: "close" 46 | -------------------------------------------------------------------------------- /.github/workflows/azure-static-web-apps-yellow-sky-07aa3e70f.yml: -------------------------------------------------------------------------------- 1 | name: Azure Static Web Apps CI/CD 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | types: [opened, synchronize, reopened, closed] 9 | branches: 10 | - main 11 | 12 | jobs: 13 | build_and_deploy_job: 14 | if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') 15 | runs-on: ubuntu-latest 16 | name: Build and Deploy Job 17 | steps: 18 | - uses: actions/checkout@v2 19 | with: 20 | submodules: true 21 | - name: Build And Deploy 22 | id: builddeploy 23 | uses: Azure/static-web-apps-deploy@v1 24 | with: 25 | azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_YELLOW_SKY_07AA3E70F }} 26 | repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments) 27 | action: "upload" 28 | ###### Repository/Build Configurations - These values can be configured to match your app requirements. ###### 29 | # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig 30 | app_location: "/" # App source code path 31 | api_location: "/" # Api source code path - optional 32 | output_location: "/public" # Built app content directory - optional 33 | ###### End of Repository/Build Configurations ###### 34 | 35 | close_pull_request_job: 36 | if: github.event_name == 'pull_request' && github.event.action == 'closed' 37 | runs-on: ubuntu-latest 38 | name: Close Pull Request Job 39 | steps: 40 | - name: Close Pull Request 41 | id: closepullrequest 42 | uses: Azure/static-web-apps-deploy@v1 43 | with: 44 | azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_YELLOW_SKY_07AA3E70F }} 45 | action: "close" 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | 24 | # nyc test coverage 25 | .nyc_output 26 | 27 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 28 | .grunt 29 | 30 | # Bower dependency directory (https://bower.io/) 31 | bower_components 32 | 33 | # node-waf configuration 34 | .lock-wscript 35 | 36 | # Compiled binary addons (https://nodejs.org/api/addons.html) 37 | build/Release 38 | 39 | # Dependency directories 40 | node_modules/ 41 | jspm_packages/ 42 | 43 | # TypeScript v1 declaration files 44 | typings/ 45 | 46 | # Optional npm cache directory 47 | .npm 48 | 49 | # Optional eslint cache 50 | .eslintcache 51 | 52 | # Optional REPL history 53 | .node_repl_history 54 | 55 | # Output of 'npm pack' 56 | *.tgz 57 | 58 | # Yarn Integrity file 59 | .yarn-integrity 60 | 61 | # dotenv environment variables file 62 | .env 63 | .env.test 64 | 65 | # parcel-bundler cache (https://parceljs.org/) 66 | .cache 67 | 68 | # next.js build output 69 | .next 70 | 71 | # nuxt.js build output 72 | .nuxt 73 | 74 | # vuepress build output 75 | .vuepress/dist 76 | 77 | # Serverless directories 78 | .serverless/ 79 | 80 | # FuseBox cache 81 | .fusebox/ 82 | 83 | # DynamoDB Local files 84 | .dynamodb/ 85 | 86 | # TypeScript output 87 | dist 88 | out 89 | 90 | # Azure Functions artifacts 91 | bin 92 | obj 93 | appsettings.json 94 | local.settings.json 95 | 96 | node_modules 97 | 98 | /.cache 99 | /azure/function/build 100 | /public/build 101 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 14 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | @remix-run:registry=https://npm.remix.run 2 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-azuretools.vscode-azurefunctions" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Attach to Node Functions", 6 | "type": "node", 7 | "request": "attach", 8 | "port": 9229, 9 | "preLaunchTask": "func: host start" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "azureFunctions.deploySubpath": ".", 3 | "azureFunctions.postDeployTask": "npm install (functions)", 4 | "azureFunctions.projectLanguage": "TypeScript", 5 | "azureFunctions.projectRuntime": "~3", 6 | "debug.internalConsoleOptions": "neverOpen", 7 | "azureFunctions.preDeployTask": "npm prune (functions)" 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "func", 6 | "command": "host start", 7 | "problemMatcher": "$func-node-watch", 8 | "isBackground": true, 9 | "dependsOn": "npm build (functions)" 10 | }, 11 | { 12 | "type": "shell", 13 | "label": "npm build (functions)", 14 | "command": "npm run build", 15 | "dependsOn": "npm install (functions)", 16 | "problemMatcher": "$tsc" 17 | }, 18 | { 19 | "type": "shell", 20 | "label": "npm install (functions)", 21 | "command": "npm install" 22 | }, 23 | { 24 | "type": "shell", 25 | "label": "npm prune (functions)", 26 | "command": "npm prune --production", 27 | "dependsOn": "npm build (functions)", 28 | "problemMatcher": [] 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Welcome to Remix! 2 | 3 | - [Remix Docs](https://docs.remix.run) 4 | - [Customer Dashboard](https://remix.run/dashboard) 5 | 6 | ## Development 7 | 8 | From your terminal: 9 | 10 | ```sh 11 | npm run dev 12 | ``` 13 | -------------------------------------------------------------------------------- /app/entry.client.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from "react-dom"; 2 | import { RemixBrowser } from "remix"; 3 | 4 | ReactDOM.hydrate(, document); 5 | -------------------------------------------------------------------------------- /app/entry.server.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOMServer from "react-dom/server"; 2 | import type { EntryContext } from "remix"; 3 | import { RemixServer } from "remix"; 4 | 5 | export default function handleRequest( 6 | request: Request, 7 | responseStatusCode: number, 8 | responseHeaders: Headers, 9 | remixContext: EntryContext 10 | ) { 11 | let markup = ReactDOMServer.renderToString( 12 | 13 | ); 14 | 15 | responseHeaders.set("Content-Type", "text/html"); 16 | 17 | return new Response("" + markup, { 18 | status: responseStatusCode, 19 | headers: responseHeaders, 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /app/root.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Links, 3 | LinksFunction, 4 | LiveReload, 5 | LoaderFunction, 6 | Meta, 7 | Outlet, 8 | Scripts, 9 | ScrollRestoration, 10 | useLoaderData, 11 | } from "remix"; 12 | 13 | import stylesUrl from "./styles/global.css"; 14 | 15 | export let links: LinksFunction = () => { 16 | return [{ rel: "stylesheet", href: stylesUrl }]; 17 | }; 18 | 19 | export let loader: LoaderFunction = async () => { 20 | return { date: new Date() }; 21 | }; 22 | 23 | function Document({ children }: { children: React.ReactNode }) { 24 | return ( 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | {children} 34 | 35 | 36 | 37 | 38 | 39 | ); 40 | } 41 | 42 | export default function App() { 43 | let data = useLoaderData(); 44 | return ( 45 | 46 | 47 |
48 |

This page was rendered at {data.date.toLocaleString()}

49 |
50 |
51 | ); 52 | } 53 | 54 | export function ErrorBoundary({ error }: { error: Error }) { 55 | return ( 56 | 57 |

App Error

58 |
{error.message}
59 |

60 | Replace this UI with what you want users to see when your app throws 61 | uncaught errors. 62 |

63 |
64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /app/routes/404.tsx: -------------------------------------------------------------------------------- 1 | import type { MetaFunction } from "remix"; 2 | 3 | export let meta: MetaFunction = () => { 4 | return { title: "Ain't nothing here" }; 5 | }; 6 | 7 | export default function FourOhFour() { 8 | return ( 9 |
10 |

404

11 |
12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /app/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Link, 3 | LinksFunction, 4 | LoaderFunction, 5 | MetaFunction, 6 | useLoaderData, 7 | } from "remix"; 8 | 9 | import stylesUrl from "../styles/index.css"; 10 | 11 | export let meta: MetaFunction = () => { 12 | return { 13 | title: "Remix Starter", 14 | description: "Welcome to remix!", 15 | }; 16 | }; 17 | 18 | export let links: LinksFunction = () => { 19 | return [{ rel: "stylesheet", href: stylesUrl }]; 20 | }; 21 | 22 | export let loader: LoaderFunction = async () => { 23 | return { message: "this is awesome 😎" }; 24 | }; 25 | 26 | export default function Index() { 27 | let data = useLoaderData(); 28 | 29 | return ( 30 |
31 |

Welcome to Remix on Azure!

32 | Go to page 2 33 |

34 | Check out the docs to get started. 35 |

36 |

Message from the loader: {data.message}

37 |
38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /app/routes/page-2.tsx: -------------------------------------------------------------------------------- 1 | import { RouteComponent, MetaFunction, Link } from "remix"; 2 | 3 | const meta: MetaFunction = () => ({ 4 | title: "Page 2", 5 | }); 6 | 7 | const Page: RouteComponent = () => { 8 | return ( 9 | <> 10 |

Welcome to Page 2👋

11 | Go back home. 12 | 13 | ); 14 | }; 15 | 16 | export default Page; 17 | export { meta }; 18 | -------------------------------------------------------------------------------- /app/styles/global.css: -------------------------------------------------------------------------------- 1 | :focus:not(:focus-visible) { 2 | outline: none; 3 | } 4 | 5 | body { 6 | font-family: sans-serif; 7 | } 8 | 9 | footer { 10 | text-align: center; 11 | color: #ccc; 12 | padding-top: 80px; 13 | } 14 | -------------------------------------------------------------------------------- /app/styles/index.css: -------------------------------------------------------------------------------- 1 | /* 2 | * when the user visits this page, this style will apply, when they leave, it 3 | * will get unloaded, so don't worry so much about conflicting styles between 4 | * pages! 5 | */ 6 | -------------------------------------------------------------------------------- /azure/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindings": [ 3 | { 4 | "authLevel": "anonymous", 5 | "type": "httpTrigger", 6 | "direction": "in", 7 | "name": "req" 8 | }, 9 | { 10 | "type": "http", 11 | "direction": "out", 12 | "name": "$return" 13 | } 14 | ], 15 | "scriptFile": "./function/index.js" 16 | } 17 | -------------------------------------------------------------------------------- /azure/function/handler.js: -------------------------------------------------------------------------------- 1 | const { 2 | createRequestHandler: createRemixRequestHandler, 3 | } = require("@remix-run/server-runtime"); 4 | 5 | const { 6 | formatServerError, 7 | Headers: NodeHeaders, 8 | Request: NodeRequest, 9 | Response: NodeResponse, 10 | RequestInit: NodeRequestInit, 11 | } = require("@remix-run/node"); 12 | 13 | function createRequestHandler({ 14 | build, 15 | getLoadContext, 16 | mode = process.env.NODE_ENV, 17 | }) { 18 | let platform = { formatServerError }; 19 | let handleRequest = createRemixRequestHandler(build, platform, mode); 20 | 21 | return async (_context, req) => { 22 | let request = createRemixRequest(req); 23 | let loadContext = 24 | typeof getLoadContext === "function" ? getLoadContext(req) : undefined; 25 | 26 | let response = await handleRequest(request, loadContext); 27 | 28 | return { 29 | status: response.status, 30 | headers: response.headers.raw(), 31 | body: await response.text(), 32 | }; 33 | }; 34 | } 35 | 36 | function createRemixHeaders(requestHeaders) { 37 | let headers = new NodeHeaders(); 38 | 39 | for (let [key, value] of Object.entries(requestHeaders)) { 40 | if (!value) continue; 41 | headers.set(key, value); 42 | } 43 | 44 | return headers; 45 | } 46 | 47 | function createRemixRequest(req) { 48 | let url = req.headers["x-ms-original-url"]; 49 | 50 | let init = { 51 | method: req.method || "GET", 52 | headers: createRemixHeaders(req.headers), 53 | }; 54 | 55 | if (req.body && req.method !== "GET" && req.method !== "HEAD") { 56 | init.body = req.body; 57 | } 58 | 59 | return new NodeRequest(url, init); 60 | } 61 | 62 | module.exports = { createRequestHandler }; 63 | -------------------------------------------------------------------------------- /azure/function/index.js: -------------------------------------------------------------------------------- 1 | const { installGlobals } = require("@remix-run/node"); 2 | const { createRequestHandler } = require("./handler"); 3 | 4 | installGlobals(); 5 | 6 | module.exports = createRequestHandler({ build: require("./build") }); 7 | -------------------------------------------------------------------------------- /host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0", 3 | "logging": { 4 | "applicationInsights": { 5 | "samplingSettings": { 6 | "isEnabled": true, 7 | "excludedTypes": "Request" 8 | } 9 | } 10 | }, 11 | "extensions": { 12 | "http": { 13 | "routePrefix": "api" 14 | } 15 | }, 16 | "extensionBundle": { 17 | "id": "Microsoft.Azure.Functions.ExtensionBundle", 18 | "version": "[2.*, 3.0.0)" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "remix-on-azure", 4 | "description": "", 5 | "license": "", 6 | "scripts": { 7 | "build": "remix build", 8 | "dev": "cross-env NODE_ENV=development swa start ./public --run \"remix dev\" --api-location .", 9 | "start": "remix-serve build", 10 | "prepare": "remix setup node" 11 | }, 12 | "dependencies": { 13 | "@remix-run/dev": "1.1.3", 14 | "@remix-run/node": "1.1.3", 15 | "@remix-run/react": "1.1.3", 16 | "@remix-run/serve": "1.1.3", 17 | "react": "17.0.2", 18 | "react-dom": "17.0.2", 19 | "remix": "1.1.3" 20 | }, 21 | "devDependencies": { 22 | "@azure/functions": "3.0.0", 23 | "@azure/static-web-apps-cli": "0.8.2", 24 | "@types/react": "17.0.38", 25 | "@types/react-dom": "17.0.11", 26 | "cross-env": "7.0.3", 27 | "typescript": "4.5.4" 28 | }, 29 | "engines": { 30 | "node": ">=14" 31 | }, 32 | "sideEffects": false 33 | } 34 | -------------------------------------------------------------------------------- /proxies.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/proxies", 3 | "proxies": {} 4 | } 5 | -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcansh/remix-on-azure/fb8e391c7627397340de0da3de126ecf1d90e109/public/favicon.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Remix 8 | 9 | 10 |

11 | This is a placeholder file you should never see, we'll rewrite all 12 | requests to our azure function 13 |

14 | 15 | 16 | -------------------------------------------------------------------------------- /public/staticwebapp.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "platform": { 3 | "apiRuntime": "node:16" 4 | }, 5 | "routes": [ 6 | { 7 | "route": "/", 8 | "rewrite": "/api/azure" 9 | }, 10 | { 11 | "route": "/index.html", 12 | "rewrite": "/api/azure" 13 | } 14 | ], 15 | "navigationFallback": { 16 | "rewrite": "/api/azure" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /remix.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | appDirectory: "app", 3 | browserBuildDirectory: "public/build", 4 | publicPath: "/build/", 5 | serverBuildDirectory: "azure/function/build", 6 | devServerPort: 8002, 7 | }; 8 | -------------------------------------------------------------------------------- /remix.env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"], 3 | "compilerOptions": { 4 | "forceConsistentCasingInFileNames": true, 5 | "lib": ["DOM", "DOM.Iterable", "ES2019"], 6 | "isolatedModules": true, 7 | "esModuleInterop": true, 8 | "jsx": "react-jsx", 9 | "moduleResolution": "node", 10 | "resolveJsonModule": true, 11 | "target": "ES2019", 12 | "strict": true, 13 | "baseUrl": ".", 14 | "paths": { 15 | "~/*": ["./app/*"] 16 | }, 17 | 18 | // Remix takes care of building everything in `remix build`. 19 | "noEmit": true 20 | } 21 | } 22 | --------------------------------------------------------------------------------