diff --git a/pkg/eval/builtin_fn_flow.go b/pkg/eval/builtin_fn_flow.go
index 57f27cda..0f90ab22 100644
--- a/pkg/eval/builtin_fn_flow.go
+++ b/pkg/eval/builtin_fn_flow.go
@@ -283,14 +283,14 @@ func multiErrorFn(excs ...Exception) error {
 //elvdoc:fn return
 //
 // Raises the special "return" exception. When raised inside a named function
-// (defined by the [`fn` keyword](../language.html#function-definition-fn)) it
-// is captured by the function and causes the function to terminate. It is not
-// captured by an anonymous function (aka [lambda](../language.html#lambda)).
+// (defined by the [`fn` keyword](language.html#fn)) it is captured by the
+// function and causes the function to terminate. It is not captured by an
+// ordinary anonymous function.
 //
 // Because `return` raises an exception it can be caught by a
-// [`try`](language.html#exception-control-try) block. If not caught, either
-// implicitly by a named function or explicitly, it causes a failure like any
-// other uncaught exception.
+// [`try`](language.html#try) block. If not caught, either implicitly by a
+// named function or explicitly, it causes a failure like any other uncaught
+// exception.
 //
 // See the discussion about [flow commands and
 // exceptions](language.html#exception-and-flow-commands)
@@ -330,9 +330,8 @@ func returnFn() error {
 // captured and causes the loop to terminate.
 //
 // Because `break` raises an exception it can be caught by a
-// [`try`](language.html#exception-control-try) block. If not caught, either
-// implicitly by a loop or explicitly, it causes a failure like any other
-// uncaught exception.
+// [`try`](language.html#try) block. If not caught, either implicitly by a loop
+// or explicitly, it causes a failure like any other uncaught exception.
 //
 // See the discussion about [flow commands and exceptions](language.html#exception-and-flow-commands)
 //
@@ -357,9 +356,8 @@ func breakFn() error {
 // captured and causes the loop to begin its next iteration.
 //
 // Because `continue` raises an exception it can be caught by a
-// [`try`](language.html#exception-control-try) block. If not caught, either
-// implicitly by a loop or explicitly, it causes a failure like any other
-// uncaught exception.
+// [`try`](language.html#try) block. If not caught, either implicitly by a loop
+// or explicitly, it causes a failure like any other uncaught exception.
 //
 // See the discussion about [flow commands and exceptions](language.html#exception-and-flow-commands)
 //
diff --git a/pkg/eval/builtin_fn_misc.go b/pkg/eval/builtin_fn_misc.go
index f67764ee..c030b23a 100644
--- a/pkg/eval/builtin_fn_misc.go
+++ b/pkg/eval/builtin_fn_misc.go
@@ -376,7 +376,7 @@ var TimeAfter = func(fm *Frame, d time.Duration) <-chan time.Time {
 // contexts that might be executing in parallel as a consequence of a command
 // such as [`peach`](#peach).
 //
-// A duration can be a simple [number](../language.html#number) (with optional
+// A duration can be a simple [number](language.html#number) (with optional
 // fractional value) without an explicit unit suffix, with an implicit unit of
 // seconds.
 //
diff --git a/pkg/eval/builtin_fn_num.go b/pkg/eval/builtin_fn_num.go
index 57e42327..c48bac03 100644
--- a/pkg/eval/builtin_fn_num.go
+++ b/pkg/eval/builtin_fn_num.go
@@ -69,7 +69,7 @@ func init() {
 // command outputs it as is.
 //
 // This command is usually not needed for working with numbers; see the
-// discussion of [numerical commands](#numerical-commands).
+// discussion of [numeric commands](#numeric-commands).
 //
 // Examples:
 //
diff --git a/pkg/mods/file/file.go b/pkg/mods/file/file.go
index 50addde0..067d4ef0 100644
--- a/pkg/mods/file/file.go
+++ b/pkg/mods/file/file.go
@@ -66,7 +66,7 @@ func close(f vals.File) error {
 // ```
 //
 // Create a new pipe that can be used in redirections. A pipe contains a read-end and write-end.
-// Each pipe object is a [pseudo-map](#pseudo-map) with fields `r` (the read-end [file
+// Each pipe object is a [pseudo-map](language.html#pseudo-map) with fields `r` (the read-end [file
 // object](./language.html#file)) and `w` (the write-end).
 //
 // When redirecting command input from a pipe with `<`, the read-end is used. When redirecting
diff --git a/pkg/mods/path/path.go b/pkg/mods/path/path.go
index f8c67587..d4df5195 100644
--- a/pkg/mods/path/path.go
+++ b/pkg/mods/path/path.go
@@ -267,8 +267,8 @@ func tempDir(opts mktempOpt, args ...string) (string, error) {
 // defaults to `elvish-*`.
 //
 // It is the caller's responsibility to close the file with
-// [`file:close`](file.html#close). The caller should also remove the file if it
-// is intended to be temporary (with `rm $f[name]`).
+// [`file:close`](file.html#fileclose). The caller should also remove the file
+// if it is intended to be temporary (with `rm $f[name]`).
 //
 // ```elvish-transcript
 // ~> f = path:temp-file
diff --git a/pkg/mods/str/str.go b/pkg/mods/str/str.go
index 9ac02864..e0f0f135 100644
--- a/pkg/mods/str/str.go
+++ b/pkg/mods/str/str.go
@@ -352,7 +352,7 @@ func join(sep string, inputs eval.Inputs) (string, error) {
 // non-negative, it determines the max number of substitutions.
 //
 // **Note**: This command does not support searching by regular expressions, `$old`
-// is always interpreted as a plain string. Use [re:replace](re.html#replace) if
+// is always interpreted as a plain string. Use [re:replace](re.html#rereplace) if
 // you need to search by regex.
 
 type maxOpt struct{ Max int }
@@ -388,7 +388,7 @@ func replace(opts maxOpt, old, repl, s string) string {
 // ```
 //
 // **Note**: This command does not support splitting by regular expressions,
-// `$sep` is always interpreted as a plain string. Use [re:split](re.html#split)
+// `$sep` is always interpreted as a plain string. Use [re:split](re.html#resplit)
 // if you need to split by regex.
 //
 // Etymology: Various languages, in particular
@@ -440,7 +440,7 @@ func split(fm *eval.Frame, opts maxOpt, sep, s string) error {
 //
 // The output format is subject to change.
 //
-// @cf from-codepoints
+// @cf str:from-codepoints
 
 func toCodepoints(fm *eval.Frame, s string) error {
 	out := fm.ValueOutput()
@@ -489,7 +489,7 @@ func toCodepoints(fm *eval.Frame, s string) error {
 //
 // The output format is subject to change.
 //
-// @cf from-utf8-bytes
+// @cf str:from-utf8-bytes
 
 func toUtf8Bytes(fm *eval.Frame, s string) error {
 	out := fm.ValueOutput()
diff --git a/pkg/mods/unix/umask.go b/pkg/mods/unix/umask.go
index 8b5efa9e..b44ecefb 100644
--- a/pkg/mods/unix/umask.go
+++ b/pkg/mods/unix/umask.go
@@ -39,7 +39,7 @@ const (
 // restored. **Warning**: Since the umask applies to the entire process, not
 // individual threads, changing it temporarily in this manner is dangerous if
 // you are doing anything in parallel. Such as via the
-// [`peach`](ref/builtin.html#peach) command.
+// [`peach`](builtin.html#peach) command.
 
 // UmaskVariable is a variable whose value always reflects the current file
 // creation permission mask. Setting it changes the current file creation
diff --git a/website/get/prelude.md b/website/get/prelude.md
index ebcfbcfc..ad8bdadf 100644
--- a/website/get/prelude.md
+++ b/website/get/prelude.md
@@ -268,7 +268,8 @@ apt install elvish
 
 However, only testing versions of Debian and Ubuntu tend to have the latest
 Elvish release. If you are running a stable release of Debian or Ubuntu, it is
-recommended to use official [prebuilt binaries](#prebuilt-binaries) instead.
+recommended to [install an official binaries](#installing-an-official-binary)
+instead.
 
 ## macOS
 
diff --git a/website/learn/unique-semantics.md b/website/learn/unique-semantics.md
index 03d5ac7c..19be4171 100644
--- a/website/learn/unique-semantics.md
+++ b/website/learn/unique-semantics.md
@@ -4,8 +4,7 @@
 
 The semantics of Elvish is unique in many aspects when compared to other shells.
 This can be surprising if you are used to other shells, and it is a result of
-the [design choice](../ref/philosophy.html) of making Elvish a full-fledged
-programming language.
+the design choice of making Elvish a full-fledged programming language.
 
 # Structureful IO
 
@@ -202,7 +201,7 @@ echo "after false"
 An alternative way to describe this is that Elvish **does** have exit statuses,
 but non-zero exit statuses terminates execution by default. You can handle
 non-zero exit statuses by wrapping the command in a
-[`try`](../language.html#exception-control-try) block.
+[`try`](../ref/language.html#try) block.
 
 Compare with POSIX shells, the behavior of Elvish is similar to `set -e` or
 `set -o errexit`, or having implicit `&&` operators joining all the commands.
diff --git a/website/ref/epm.md b/website/ref/epm.md
index a324144a..98e3ce44 100644
--- a/website/ref/epm.md
+++ b/website/ref/epm.md
@@ -50,7 +50,7 @@ manages one of them:
 -   On Windows, `epm` manages `%LocalAppData%\elvish\lib`.
 
 This directory is called the `epm`-managed directory, and its path is available
-as [`$epm:managed-dir`](#epm-managed-dir).
+as [`$epm:managed-dir`](#epmmanaged-dir).
 
 # Custom package domains
 
diff --git a/website/ref/language.md b/website/ref/language.md
index 986b73fd..41024597 100644
--- a/website/ref/language.md
+++ b/website/ref/language.md
@@ -436,7 +436,7 @@ turn a pseudo-map. The reason pseudo-map has has a `type` field identifying how
 the exception was raised, and further fields depending on the type:
 
 -   If the `type` field is `fail`, the exception was raised by the
-    [fail](builtins.html#fail) command.
+    [fail](builtin.html#fail) command.
 
     In this case, the `content` field contains the argument to `fail`.
 
@@ -498,8 +498,9 @@ Examples:
 ## File
 
 There is no literal syntax for the file type. This type is returned by commands
-such as [file:open](file.html#open) and [path:temp-file](path.html#temp-file).
-It can be used as the target of a redirection rather than a filename.
+such as [file:open](file.html#fileopen) and
+[path:temp-file](path.html#pathtemp-file). It can be used as the target of a
+redirection rather than a filename.
 
 A file object is a [pseudo-map](#pseudo-map) with fields `fd` (an int) and
 `name` (a string). If the file is closed the fd will be -1.
@@ -2419,7 +2420,7 @@ order:
    the `use` command.
 
 1. **User defined**: These match a [user defined module](#user-defined-modules)
-   in a [module search directory](#module-search-directories).
+   in a [module search directory](command.html#module-search-directories).
 
 1. **Pre-defined**: These match the name of a
    [pre-defined module](#pre-defined-modules), such as `math` or `str`.
diff --git a/website/ref/readline-binding.md b/website/ref/readline-binding.md
index 138a51f5..687c91ba 100644
--- a/website/ref/readline-binding.md
+++ b/website/ref/readline-binding.md
@@ -14,7 +14,7 @@ use readline-binding
 
 Note that this will override some of the standard bindings. For example, <span
 class="key">Ctrl-L</span> will be bound to a function that clears the terminal
-screen rather than start [location mode](../learn/cookbook.html).
+screen rather than start location mode.
 
 See the
 [source code](https://github.com/elves/elvish/blob/master/pkg/mods/bundled/readline-binding.elv.go)