Writing documentation is relatively easy. Writing clear documentation is hard. Writing clear documentation and ensuring it stays up to date is, if not an order of magnitude, at left several times harder than doing the same thing in code. To the point where it's less effort to re-answer the same questions over and over in an issue tracker (and others can help keep track of your responses) than to maintain up to date docs.
My preferred approach, on smaller projects with limited numbers of people, is to push documentation as close to the code as possible. Prefer comments, tests, and types over standalone docs, in that order. Prefer not to duplicate the code (explain why, not what, is being done; consider using intermediate variables with descriptive names rather than a comment on "what"), unless it is a public interface and you want to restrict the intended behavior to some subset of the actual behavior. Your standalone docs are now about the high level stuff, filling in the gaps between file-level comments and directing the reader to the right files/code to look at. Generated documentation from the types and comments helps make this approach more readable.
Here's an example: a project I'm working on now had several shell scripts to automate common tasks (e.g. setup). Each script starts with:
#!/bin/sh
usage()
{
cat >&2 <<'USAGE'
Description here
USAGE
}
set -eu
trap 'test $? -eq 0 || usage' EXIT
In other words, an explanation of how the script is intended to be used is embedded at the top of the file, and printed any time there is an error (e.g. invalid arguments). And the readme clearly states that if you want to understand the scripts, you should go read them. This is so much easier to maintain than a comment in a standalone doc, that you KNOW I'm going to forget to update when making tweaks to the script. And although I'm not doing it now, it is machine parse-able if I wanted to generate docs from it.