{"id":26788613,"url":"https://github.com/bmeverett/blog-stack","last_synced_at":"2026-06-25T06:31:18.681Z","repository":{"id":77557016,"uuid":"486153652","full_name":"bmeverett/blog-stack","owner":"bmeverett","description":"Remix Blog Template Stack","archived":false,"fork":false,"pushed_at":"2022-04-27T12:58:30.000Z","size":2263,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-09-11T00:41:30.740Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bmeverett.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-04-27T10:52:22.000Z","updated_at":"2022-04-27T10:56:02.000Z","dependencies_parsed_at":null,"dependency_job_id":"e48be4d6-9f13-4314-be51-519e08132849","html_url":"https://github.com/bmeverett/blog-stack","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/bmeverett/blog-stack","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bmeverett%2Fblog-stack","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bmeverett%2Fblog-stack/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bmeverett%2Fblog-stack/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bmeverett%2Fblog-stack/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bmeverett","download_url":"https://codeload.github.com/bmeverett/blog-stack/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bmeverett%2Fblog-stack/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34763481,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-25T02:00:05.521Z","response_time":101,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-03-29T13:18:30.021Z","updated_at":"2026-06-25T06:31:18.674Z","avatar_url":"https://github.com/bmeverett.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Remix Speed Metal Stack\n\nLearn more about [Remix Stacks](https://remix.run/stacks).\n\n```sh\nnpx create-remix --template Girish21/speed-metal-stack\n```\n\n## Remix Blog 📖\n\nThis blog starter template was inspired heavily by Kent C. Dodds implementation of [kentcdodds.com][kcd]. You can read more about the architecture and the idea behind it at [How I built a modern website in 2021][kcd-arch].\n\n## Architecture 💡\n\n![website-architecture](./images/fly-sqlite-arch.png)\n\n## Important 🚧\n\nFly requires a globally unique name for all the apps, and we've used the directory name and random hash as the app name. Of course, you can change this anytime you want BEFORE launching the app with Fly CLI. But it's not a big deal since you can reassign the internal Fly URL to any custom domain by adding a [`CNAME`][cname] record to your custom domain pointing to the Fly internal URL. We'll see that later when deploying the app to production.\n\n## Quickstart\n\n```sh\n# run database migrations and set up the initial blog\nnpm run setup\n# run the initial build for the development server\nnpm run build\n# start the development server and run other processes in parallel in watch mode\nnpm run dev\n```\n\n## Available scripts\n\n- `build` - compile and build the express server, Remix app, Tailwind in `production` mode\n- `dev` - starts the express server, Remix watcher, Tawilwind CLI in watch mode\n- `format` - runs prettier on the codebase and fixes fixable issues\n- `lint` - runs ESLint on the codebase\n- `new:blog` - create a new Blog post template from the command line\n- `start` - starts the express server (should only be executed after running `npm run build`)\n- `test` - runs `vitest`\n- `test:e2e:dev` - starts the cypress runner in development mode\n- `test:e2e:run` - starts the cypress runner in CI mode\n- `typecheck` - runs type check on the codebase\n\n## Fly Setup 🛠\n\n1. [Install Fly](https://fly.io/docs/getting-started/installing-flyctl/)\n\n2. Sign up and log in to Fly\n\n   ```sh\n   flyctl auth signup\n   ```\n\n## Database 🗃\n\nWe use [SQLite][sqlite] as the database in this template. SQLite is a fast database engine, a great option to persist data without reaching for advanced database engines like Postgres.\n\n### Installation ⚙️\n\nSQLite comes pre-installed on all Macs. You can check the official installation guides for other OS's [SQLite Installation Guides][sqlite-installation]\n\n### Why do we need a database ❓\n\nWe use [MDX-Bundler][mdx-bundler] to compile the MDX files, and MDX-Bundler uses `esbuild` internally to compile the MDX files. Though `esbuild` is very fast, there is a noticeable lag during this process which is not suitable for the performance of the site and the user experience. And since there is no need to compile MDX on every request when the data does not change seems like a waste of time and performance. So instead, we can cache the compiled MDX and recompile it only when we know the content has changed.\n\n### Prisma △\n\nWe use [Prisma][prisma] as the ORM in this template. To create the SQLite database and initialise the database schema, run:\n\n```sh\nnpx prisma migrate dev\n```\n\nThe above command will prompt for a migration name, and you can name it as `initial migration`. This command will also install Prisma Client for interacting with the database.\n\n## Development 💻\n\nWe can start our development server with the migrations run and the SQLite database populated with the initial schema. Then, from a new tab in your terminal, run the command.\n\n```sh\nnpm run dev\n```\n\nThis command starts four processes concurrently.\n\n- The Remix dev server starts in development mode and rebuilds assets on file change.\n- Tailwind CLI which rebuilds the stylesheet when the styles change\n- An [MSW][msw] server which intercepts the API calls to GitHub and serves the content from the local instead of calling the remote API\n- A file watcher watches over the `content` directory and rebuilds the assets.\n\n### Relavant files 🔍\n\n- Tailwind config [tailwind.config.js](./tailwind.config.js)\n- MSW API mock server [mock](./mocks/start.ts)\n- Content change watcher [refresh-on-content-change](./others/refresh-on-content-change.ts)\n\n## Deployment 🚀\n\n### Initial setup 👀\n\nBefore proceeding to deploy our app, we have some steps to take care of:\n\n- Create a GitHub account [GitHub](https://repo.new)\n- Create a new app on Fly\n\n```sh\nflyctl launch --name blog-stack-3926 --copy-config --no-deploy\n```\n\n\u003e ⚠️ Remember not to deploy since we have some setup steps left to complete!\n\n### Environment variables and Secrets 🤫\n\nThis template comes with GitHub actions workflows to automatically deploy the app to Fly.io. First, we need to set up our GitHub actions and the Fly app with some secrets. Let's do that now.\n\nTo push the build image to the remote Fly registry from GitHub action, we need an access token from Fly. We can generate that using the Fly command line, run:\n\n```sh\nflyctl auth token\n```\n\nThe command will generate an access token. You can then add this token to your GitHub actions secrets by visiting your GitHub repository's `settings` page `https://github.com/:owner/:repo/settings/secrets/actions` and then click `New repository secret`. Next, GitHub will prompt for a key and a value. The key should be `FLY_API_TOKEN`, and the value will be the token generated by the command line.\n\nWe also need to set the Fly app name as a secret, the key should be `FLY_APP_NAME`, and the value will be the app name specified in [fly.toml](./fly.toml)\n\nNow we need to set up secrets in our Fly app.\n\nSince we're fetching the content from GitHub on demand instead of building all the pages upfront, we need an access token from GitHub to call the GitHub API and fetch the content. Also, GitHub won't rate-limit the app from calling the GitHub API more often. So, you can generate an access token at [Personal access token](https://github.com/settings/tokens). Then, you can copy the generated token and set it to your app's secret. We can do that by running the following command:\n\n```sh\nflyctl secrets set GITHUB_TOKEN={GITHUB_TOKEN}\n```\n\nWe also need a secret to sign our session. We can do that by running the command:\n\n```sh\nflyctl secrets set SESSION_SECRETS=$(openssl rand -hex 32)\n```\n\n\u003e If `openssl` is not available, you can generate a secure token using a password generating service like [`1Password`][generate-password].\n\nThe last secret required is a token for securely communicating between the GitHub action and our app deployed on a remote server since we need a public-facing API for this communication.\n\n```sh\nopenssl rand -hex 32\n```\n\nWe have to set this secret as part of the GitHub actions secret and a Fly secret. The key should be `REFRESH_TOKEN`. You can create a new actions secret in GitHub and create a new secret for the Fly app by running the command.\n\n```sh\nflyctl secrets set REFRESH_TOKEN={GENERATED_PASSWORD}\n```\n\n### Volumes 💾\n\nWe also need to create a volume in Fly to persist our app data (SQLite DB) so that Fly can persist the data stored across deployments and container restarts. Again, we can do that using the Fly command line.\n\n```sh\nflyctl volumes create data --region [REGION] --size 1\n```\n\n\u003e Note: REGION should be the region selected when launching the app. You can check the region chosen by running `flyctl regions list`.\n\nIt's important to note that Volumes are bound to an app in a region and cannot be shared between apps in the same region or across multiple regions.\n\nYou can learn more about Fly Volumes [here][volumes]\n\n### Push to Prod 🥳\n\nWe are ready for our first deployment. GitHub actions workflows are configured to run on push to the `main` branch. So let's push the local branch `main` to remote, triggering the workflows.\n\nOnce all the checks are passed, and the deployment is complete, you can run:\n\n```sh\nflyctl info\n```\n\nTo get the current app URL and IP address. The app URL will be `https://blog-stack-3926.fly.dev`. You can visit that URL, and the site should be online. That's it. You have deployed your blog built using REMIX!.\n\n### Adding Custom Domain 🔖\n\nTo add a custom domain to the app, you first must buy a domain from a Domain Name Register, and you can choose one of your preferences. Some popular options are [Domain.com](https://www.domain.com/), [Google](https://domains.google.com/registrar), [Cloudflare](https://www.cloudflare.com/en-gb/products/registrar/).\n\nAfter buying the domain, we can add a DNS record to point to the domain or create a subdomain and point that to the Fly app URL. We can do that by adding a DNS record using the CNAME option and entering the Fly URL `https://blog-stack-3926.fly.dev`.\n\nWe also have to create an SSL certificate on Fly with the domain name. We can do that by running the command:\n\n```sh\nflyctl cert create [DOMAIN]\n```\n\nYou can read more about this at [SSL for Custom Domains](https://fly.io/docs/app-guides/custom-domains-with-fly/)\n\nThat's it, and we are ready to share our blog with the rest of the world! But there is one more step to take care of before sharing it.\n\n### Scaling ⚖️\n\nThere are two ways of scaling an application, vertical and horizontal.\n\nIn vertical scaling, the system is scaled by adding more compute resources to the server (increasing the CPU/RAM). Fly supports vertical scaling, and you can check the docs here [scaling virtual machines](https://fly.io/docs/reference/scaling/#scaling-virtual-machines).\n\nHorizontal scaling is achieved by adding more replicas of the same application, either in the same region or in other regions worldwide. Fly supports many regions worldwide, and you can read more about them here [Fly regions](https://fly.io/docs/reference/scaling/#scaling-virtual-machines).\n\nOur app is currently deployed in only one region we selected when we ran `flyctl launch`. This is fine during prototyping and development, but when pushing for production, we would want our app to be accessible from regions worldwide and have similar performances for users worldwide. In this case, we can add more replicas of our app worldwide, at least in the regions with many targetted users, so that the app will run on the servers closer to the users, and all the users will have comparable performance.\n\nSince Fly [anchors the regions](https://fly.io/docs/reference/volumes/#creating-volumes) based on the volumes created, we can add more regions by creating a volume in the new region or adding a replica in the same region. For example, we can do that by:\n\n```sh\nflyctl volumes create data --region [NEW_REGION] --size 1\n```\n\n\u003e You can check this list of available regions at [Fly regions][fly-regions]\n\nAnd then increase the [scale count](https://fly.io/docs/reference/scaling/#count-scaling) of the Fly app by running the command:\n\n```sh\nflyctl scale count 2\n```\n\n\u003e The above command will set the scale count to 2, meaning two instances of our app will run on the specified regions where the volumes are created.\n\nYou can read more about scaling at [Fly scaling][fly-scaling]\n\n## API Mocks 🔸\n\nOur architecture is to fetch the blog content from the GitHub repository on demand and not bundle the content as part of the build. Therefore, we will be making a significant amount of API calls to GitHub. And with any API, there come restrictions such as rate limit, calls per minute, etc. And when we're writing an article, the process becomes tedious since we're making calls to the GitHub API; the article has to be on GitHub so that the API can return the content. This process is not ideal. We can do better.\n\nInstead, we can mock the API request to GitHub and serve the articles locally, providing a better experience. Instead of calling the GitHub API, we use [MSW][msw] to intercept the request and return a mocked response that serves the content from the local file system.\n\n## Linting ⬡\n\nThis template comes preconfigured with [ESLint][eslint] and [Prettier][prettier] rules and is integrated with the workflow as a build step. For example, you can run `npm run lint` to run ESLint on the project and `npm run format` to run prettier.\n\n## Styling 💅🏻\n\nThis template comes preconfigured with [Tailwindcss][tailwind] with all the scripts required during development and production.\n\nThe template also comes with a theme toggler preconfigured and can detect the suitable theme and prevent [FART][fart]\n\n## Testing 🔬\n\nThis template comes preconfigured with [Jest][jest] and [React testing library][rtl] for unit testing and [Cypress][cypress] for e2e testing and is configured to run as part of the GitHub actions. You can run the `npm run test` command to run the Jest test suite. And `npm run test:e2e:run` to run the Cypress tests in a headless mode. You can check [package.json](./package.json) for the available commands.\n\n## Type check ʦ\n\nYou can run `npm run typecheck` to run `tsc` on the codebase. Type check is also included as part of the deployment workflow.\n\n## Debugging\n\nSome helpful commands to debug the application on Fly using the command line\n\n### Logs\n\nYou can check the logs using the command `flyctl logs` from the project directory, containing the `fly.toml` file in the project's root. You can also check the logs from the console by visiting [fly.io/apps](https://fly.io/apps).\n\n### Console\n\nYou can also log in to the remote console using the `flyctl ssh console` command.\n\n### Database\n\nAfter logging in to the console, you can also inspect the SQLite DB. But first, we have to install SQLite on the remote machine. We can do that using the `apt-get install sqlite3` command. Then, `cd` into the volume using the `cd data` command (Note: `data` refers to the volume's name created from the command line). And then run the command `sqlite3 sqlite.db` to open a command-line interface into the database.\n\n## Important links\n\n- [Remix](https://remix.run/)\n- [Remix docs](https://remix.run/docs/en/v1)\n- [Fly.io](https://fly.io/)\n- [flyctl](https://fly.io/docs/flyctl/)\n- [Fly secrets](https://fly.io/docs/reference/secrets/)\n- [Fly scaling][fly-scaling]\n- [Fly volumes](https://fly.io/docs/reference/volumes/)\n- [Fly regions][fly-regions]\n- [Fly configuration](https://fly.io/docs/reference/configuration/)\n- [MDX Bundler][mdx-bundler]\n- [SQLite][sqlite]\n\n[kcd]: https://kentcdodds.com/\n[kcd-arch]: https://kentcdodds.com/blog/how-i-built-a-modern-website-in-2021\n[cname]: https://en.wikipedia.org/wiki/CNAME_record\n[sqlite]: https://www.sqlite.org/index.html\n[mdx-bundler]: https://github.com/kentcdodds/mdx-bundler\n[sqlite-installation]: https://www.sqlite.org/download.html\n[prisma]: https://prisma.io\n[msw]: https://mswjs.io/\n[tailwind]: https://tailwindcss.com/\n[fart]: https://css-tricks.com/flash-of-inaccurate-color-theme-fart/\n[generate-password]: https://1password.com/password-generator/\n[volumes]: https://fly.io/docs/reference/volumes/\n[eslint]: https://eslint.org/\n[prettier]: https://prettier.io/\n[cypress]: https://www.cypress.io/\n[jest]: https://jestjs.io/\n[rtl]: https://testing-library.com/\n[fly-regions]: https://fly.io/docs/reference/regions/\n[fly-scaling]: https://fly.io/docs/reference/scaling/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbmeverett%2Fblog-stack","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbmeverett%2Fblog-stack","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbmeverett%2Fblog-stack/lists"}