Thanks for being willing to contribute!
Working on your first Pull Request? You can learn how from this free series How to Contribute to an Open Source Project on GitHub
Most content changes can be made without cloning the repository. Simply locate the file you wish to change in the GitHub UI, and click the little edit icon to make your change directly on the GitHub website.
If you need to make any other substantial changes, then follow the project setup steps below.
Translations for blog posts are more than welcome, but I'm afraid I don't have the bandwidth to manage them within this repository. So if you'd like to translate content on my blog, simply translate the post and put it on your own blog. Then come here and add a link to the translation in the mdx file for the post you translated. It should look like this:
---
title: Some great post
... other stuff ...
translations:
- language: Español
link: https://example.com/your/translation/link/here
author:
name: Your name
link: https://example.com/your/link/here
... more other stuff ...
---The link can be your twitter, website, or LinkedIn.
The only requirement is that at the beginning of your translation, you explain that it is a translation of the original post and link back to the original post. For example:
This is a translation of the original post Some great post by Kent C. Dodds.
If you notice an error in an existing translation or if the link to the translation is not working, please try to reach out to the translator directly to get it fixed. The only thing we can do in this repo is remove the link. If you feel that's necessary, feel free to open a pull request to do that.
Thanks for helping to make my content more accessible!
If you do need to set the project up locally yourself, feel free to follow these instructions:
- Fork and clone the repo
- Copy
services/site/.env.exampleintoservices/site/.env - Run
npm run setup -sto install dependencies and run validation - Create a branch for your PR with
git checkout -b pr/your-branch-name
Tip: Keep your
mainbranch pointing at the original repository and make pull requests from branches on your fork. To do this, run:git remote add upstream https://github.com/kentcdodds/kentcdodds.com.git git fetch upstream git branch --set-upstream-to=upstream/main mainThis will add the original repository as a "remote" called "upstream," Then fetch the git information from that remote, then set your local
mainbranch to use the upstream main branch whenever you rungit pull. Then you can make all of your pull request branches based on thismainbranch. Whenever you want to update your version ofmain, do a regulargit pull.
If the setup script doesn't work, you can try to run the commands manually:
git clone <your-fork>
cd ./kentcdodds.com
# copy the site env example to services/site/.env
# everything's mocked out during development so you shouldn't need to
# change any of these values unless you want to hit real environments.
cp services/site/.env.example services/site/.env
# Install deps
npm install
# setup database
npm exec --workspace kentcdodds.com prisma migrate reset --force
# run build, typecheck, linting
npm run validate
# setup cache database
npm run prime-cache:mocks
# Install playwright browsers
npm run test:e2e:install
# run e2e tests
npm run test:e2e:runIf that all worked without trouble, you should be able to start development with:
npm run devAnd open up http://localhost:3000 and rock!
Everything's mocked locally so you should be able to work completely offline.
The DB runs locally, but all third party endpoints are mocked out via
MSW.
Because the mdx files are built on-demand and that can take some time, we heavily cache them in sqlite. This means that if you need to work on content, you'll need a way to clear the cache. Luckily, when running the dev script, we have a file watcher that auto-updates the cache as you save the file. It should happen so fast you don't even notice what's going on, but I thought I'd mention it here just so you know if it doesn't work.
We have two kinds of tests, unit and component tests with Jest and E2E tests with Playwright.
# run the unit and component tests with jest via:
npm run test
# run the Playwright tests in dev mode:
npm run test:e2e:dev
# run the Playwright tests in headless mode:
npm run test:e2e:runEverything's set up with TypeScript/Prettier/Oxlint, plus Husky + lint-staged git hooks from the repository root:
pre-commitformats staged files with Prettier, then runs workspace linting, typechecking, and builds.pre-pushruns the workspace test suite.
After npm install, Husky installs the hooks automatically via the root
prepare script. CI workspace installs include the workspace root so the same
plain prepare command works there too.
You can run the same checks manually if you want:
npm run format
npm run format:staged
npm run lint
npm run lint:all
npm run typecheck
npm run typecheck:all
npm run build
npm run build:all
npm run precommit:verify
npm run test
npm run test:all
npm run prepush:verifyThese are all configured in the project to hopefully work with whatever editor plugins you have so it should work as you working as well.
This repo uses npm workspaces, so install dependencies from the repository root.
To run a script for a specific workspace package, use npm run <script> --workspace <package-name>.
The main site now lives in services/site, and the root npm run dev,
npm run build, npm run test, and related commands forward there.
We use Tailwind for our styles. Tailwind is configured directly in
services/site/app/styles/tailwind.css (CSS-first config) and via the Tailwind
Vite plugin in services/site/vite.config.ts.
We've got SQLite and Prisma set up. Learn about the schema and learn more about
what commands you can run in ./services/site/prisma/schema.prisma.
When changing the production schema, use an expand/contract rollout:
- Widen first: add nullable columns, add columns with safe defaults, or add new tables/indexes in a backward-compatible way.
- Deploy the widen migration and app code that can operate in both states (before and after backfill).
- Backfill data if needed.
- Narrow later: remove old columns/paths, add stricter constraints, or make fields required only after widen has been safely running in production.
Before merging the widen PR, create a follow-up issue for the narrow step and link it in the PR description. This prevents "temporary" compatibility code from becoming permanent and helps avoid schema/code rollout mismatches.
One common command you might need to run is to re-seed the database:
npm exec --workspace kentcdodds.com prisma migrate reset --forceIn addition to resetting your database to the latest schema, it'll also run the seed script which will populate the database with some example data.
fly ssh console -C bash -s
# select a specific instance
# make a backup of the database
cp /data/litefs/dbs/sqlite.db/database /data/sqlite.db.bkp
# do an integrity check
sqlite3 /data/sqlite.db.bkp "PRAGMA integrity_check;"
# make a gzip copy so it downloads faster
gzip -c /data/sqlite.db.bkp > /data/sqlite.db.bkp.gzIn another tab, download that backup:
fly sftp get -s /data/sqlite.db.bkp.gz ./sqlite.db.bkp.gz
# select the same instance as aboveWe use LiteFS to proxy the file system for SQLite for multi-regional SQLite. If things go wrong, this is what you do. First, backup the database, then scale down to a single region (one that's a primary candidate):
fly scale count 1Then delete the litefs database and import the backup:
# ssh into fly console
fly ssh console -C bash -s
# delete the litefs database
rm -rf /data/litefs/dbs/sqlite.db
# import the backup
litefs import -name sqlite.db /data/sqlite.db.bkpThen you should be good to go again.
If LiteFS is giving you grief. Then you may want to disable it. To do that, first backup the database.
In one terminal, ssh into fly:
fly ssh console -C bash
# make a copy of the database
cp /data/litefs/dbs/sqlite.db/database /data/sqlite.db
# do an integrity check
sqlite3 /data/sqlite.db "PRAGMA integrity_check;"Then make sure to scale down to a single region:
fly vol list
# grab the ID for all but the primary you want to keep
fly vol delete {id}
fly scale count 1Copy the sqlite database and the cache database to /data/litefs-disabled:
fly ssh console -C bash
cp /data/litefs/dbs/sqlite.db/database /data/litefs-disabled/sqlite.db
cp /data/litefs/dbs/cache.db/database /data/litefs-disabled/cache.dbUpdate services/site/Dockerfile:
# TODO: enable litefs
# ENV LITEFS_DIR="/litefs"
ENV LITEFS_DIR="/data/litefs-disabled"
...
# TODO: enable litefs proxy
# ENV PORT="8081"
ENV PORT="8080"
...
# TODO: enable litefs
# COPY --from=flyio/litefs:0.5.10 /usr/local/bin/litefs /usr/local/bin/litefs
# ADD other/litefs.yml /etc/litefs.yml
# RUN mkdir -p /data ${LITEFS_DIR}
# CMD ["litefs", "mount"]
CMD ["npm", "start"]NOTE: this will not run migrations or setup the swap file like it does in the current startup process. But hopefully what you're doing is temporary anyway.
And disable the litefs proxy healthcheck in fly.toml:
# TODO: enable litefs proxy
# [[services.http_checks]]
# grace_period = "10s"
# interval = "30s"
# method = "GET"
# timeout = "5s"
# path = "/litefs/health"Then push that to fly.
You'll lose any data created between when you did the backup and when the deploy finishes, but hopefully you won't have LiteFS issues.
When doing stuff like this it's not a bad idea to do a database backup first (even though Fly backs-up your volumes daily for you).
Even though fly has a specific command for adding and removing regions, when regions have volumes, you instead control the regions by adding more volumes to specific regions and then scaling up. For example:
fly vol create data --size 3 --region ams
fly scale count 2For this app, avoid bringing up many regions simultaneously. Prefer cloning one machine at a time from the current primary and waiting for checks to pass before adding the next region:
fly machine clone <PRIMARY_MACHINE_ID> -a kcd --region <REGION>
fly machine status <NEW_MACHINE_ID> -a kcd
fly checks list -a kcdSimilar to adding regions, maybe backup the data.
First, you should know which volume is the current primary region. It's kinda
hard to tell without SSH-ing into the boxes and looking for the .primary file,
but instead you can hit the site and check the x-fly-primary-instance header
which will be the hostname of the primary instance. Just make sure you don't
take that one down. If you need to change the primary, make sure to update
litefs.yml first so another region can take over as candidate.
Now that you know the volume you don't want to delete, run:
fly vol listThat'll show you all the volumes, then run:
fly vol destroy <VOL_ID>And when you're finished, scale down to the number of volumes you have:
fly scale count <COUNT>If cleanup leaves any machines in a non-started state, destroy them:
for id in $(fly m list -a kcd --json | jq -r '.[] | select(.state != "started") | .id'); do
fly machine destroy "$id" -a kcd --force
doneAfter removing regions, also clean up unattached volumes in those regions so you are not paying for orphaned storage:
for id in $(fly vol list -a kcd --json | jq -r '.[] | select(.attached_machine_id == null and (.region=="jnb" or .region=="ams" or .region=="sin" or .region=="bom" or .region=="syd" or .region=="cdg")) | .id'); do
fly vol destroy "$id" -a kcd --yes
doneRun fly vol list -a kcd first and do not delete volumes attached to active
machines.
Please checkout the open issues
Also, please watch the repo and respond to questions/bug reports/feature requests! Thanks!