diff --git a/Project.toml b/Project.toml
index 7fba92c2d..802502f84 100644
--- a/Project.toml
+++ b/Project.toml
@@ -10,12 +10,11 @@ Highlights = "eafb193a-b7ab-5a9e-9068-77385905fa72"
 JuDocTemplates = "6793090a-55ae-11e9-0511-73b91164f4ea"
 LiveServer = "16fef848-5104-11e9-1b77-fb7a48bbb589"
 Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a"
-Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
 Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
 
 [compat]
-DocStringExtensions = "^0.7.0"
-Highlights = "^0.3.0"
+DocStringExtensions = ">= 0.7.0"
+Highlights = ">= 0.3.0"
 JuDocTemplates = ">= 0.1.0"
-LiveServer = "^0.1.0"
+LiveServer = ">= 0.1.0"
 julia = "^1.0.0"
diff --git a/src/JuDoc.jl b/src/JuDoc.jl
index 427ffd488..99a1235cc 100644
--- a/src/JuDoc.jl
+++ b/src/JuDoc.jl
@@ -4,7 +4,6 @@ using JuDocTemplates
 
 using Markdown
 using Dates # see jd_vars
-using Random
 using Highlights
 
 import LiveServer
@@ -12,7 +11,6 @@ import LiveServer
 using DocStringExtensions: SIGNATURES, TYPEDEF
 
 const BIG_INT = typemax(Int)
-const JD_LEN_RANDSTRING = 4 # make this longer if you think you'll collide...
 const JD_SERVE_FIRSTCALL = Ref(true)
 
 const JD_DEBUG = Ref(false)
diff --git a/src/converter/lx.jl b/src/converter/lx.jl
index 1ca9ac32e..5fb43a87a 100644
--- a/src/converter/lx.jl
+++ b/src/converter/lx.jl
@@ -57,7 +57,7 @@ JD_LOC_EQDICT_COUNTER
 Counter to keep track of equation numbers as they appear along the page, this helps with equation
 referencing.
 """
-const JD_LOC_EQDICT_COUNTER = "COUNTER_" * randstring(JD_LEN_RANDSTRING)
+const JD_LOC_EQDICT_COUNTER = "COUNTER_XC0q"
 
 """
 $(SIGNATURES)
diff --git a/src/converter/md_blocks.jl b/src/converter/md_blocks.jl
index 526085d09..3753dbba6 100644
--- a/src/converter/md_blocks.jl
+++ b/src/converter/md_blocks.jl
@@ -74,9 +74,9 @@ function convert_mathblock(β::OCBlock, lxdefs::Vector{LxDef})::String
 
         # check if there's a label, if there is, add that to the dictionary
         matched = match(r"\\label{(.*?)}", inner)
-        
+
         if !isnothing(matched)
-            name   = refstring(strip(matched.captures[1]))
+            name   = refstring(matched.captures[1])
             write(htmls, "<a id=\"$name\"></a>")
             inner  = replace(inner, r"\\label{.*?}" => "")
             # store the label name and associated number
diff --git a/src/converter/md_utils.jl b/src/converter/md_utils.jl
index bf395585e..14a09ee32 100644
--- a/src/converter/md_utils.jl
+++ b/src/converter/md_utils.jl
@@ -9,8 +9,8 @@ processor, this is relevant for things that are parsed within latex commands etc
 function md2html(ss::AbstractString, stripp::Bool=false)::AbstractString
     isempty(ss) && return ss
 
-    # Use the base Markdown -> Html converter
-    partial = Markdown.html(Markdown.parse(ss))
+    # Use the base Markdown -> Html converter and post process headers
+    partial = ss |> Markdown.parse |> Markdown.html |> make_header_refs
 
     # In some cases, base converter adds <p>...</p>\n which we might not want
     stripp || return partial
@@ -47,3 +47,26 @@ function deactivate_divs(blocks::Vector{OCBlock})::Vector{OCBlock}
     end
     return blocks[active_blocks]
 end
+
+
+"""
+$(SIGNATURES)
+
+By default the Base Markdown to HTML converter simply converts `## ...` into headers but not
+linkable ones; this is annoying for generation of table of contents etc (and references in
+general) so this function does just that.
+"""
+function make_header_refs(h::String)::String
+    io = IOBuffer()
+    head = 1
+    for m ∈ eachmatch(r"<h([1-6])>(.*?)</h[1-6]>", h)
+        write(io, subs(h, head:m.offset-1))
+        level = m.captures[1]
+        name  = m.captures[2]
+        ref   = refstring(name)
+        write(io, "<h$level><a id=\"$ref\" href=\"#$ref\">$name</a></h$level>")
+        head = m.offset + lastindex(m.match)
+    end
+    write(io, subs(h, head:lastindex(h)))
+    return String(take!(io))
+end
diff --git a/src/misc_utils.jl b/src/misc_utils.jl
index a9b4cc30b..74cb9c370 100644
--- a/src/misc_utils.jl
+++ b/src/misc_utils.jl
@@ -123,8 +123,15 @@ mathenv(s::AbstractString)::String = "_\$>_$(s)_\$<_"
 """
 $(SIGNATURES)
 
-Creates a random string pegged to `s` that we can use for hyper-references. We could just use the
-hash but it's quite long, here the length of the output is controlled  by `JD_LEN_RANDSTRING` which
-is set to 4 by default.
-"""
-refstring(s::AbstractString)::String = randstring(MersenneTwister(hash(s)), JD_LEN_RANDSTRING)
+Takes a string `s` and replace spaces by underscores so that that we can use it
+for hyper-references. So for instance `"aa  bb"` will become `aa-bb`.
+It also defensively removes any non-word character so for instance `"aa bb !"` will be `"aa-bb"`
+"""
+function refstring(s::AbstractString)::String
+    # remove non-word characters
+    st = replace(s, r"&#[0-9]+;" => "")
+    st = replace(st, r"[^a-zA-Z0-9_\-\s]" => "")
+    st = replace(lowercase(strip(st)), r"\s+" => "-")
+    isempty(st) && return string(hash(s))
+    return st
+end
diff --git a/test/converter/markdown.jl b/test/converter/markdown.jl
index 4d7ba7f34..752ff4064 100644
--- a/test/converter/markdown.jl
+++ b/test/converter/markdown.jl
@@ -111,3 +111,18 @@ end
     hstring = J.convert_inter_html(inter_html, blocks2insert, lxcontext)
     @test hstring == "<p>text A1 text A2 blah and \nescape B1\n  text C1 \\(\\mathrm{ b}\\) text C2  then part1: AA and part2: BB.</p>\n"
 end
+
+
+@testset "headers" begin
+    st = """
+        # Title
+        and then
+        ## Subtitle cool!
+        done
+        """
+    h = st |> Markdown.parse |> Markdown.html
+    r = J.make_header_refs(h)
+    @test occursin("<h1><a id=\"title\" href=\"#title\">Title</a></h1>", r)
+    @test occursin("<h2><a id=\"subtitle-cool\" href=\"#subtitle-cool\">Subtitle cool&#33;</a></h2>", r)
+    @test occursin("<p>done</p>", r)
+end
diff --git a/test/misc.jl b/test/misc.jl
index ef5856882..d1eb2a263 100644
--- a/test/misc.jl
+++ b/test/misc.jl
@@ -74,3 +74,14 @@ end
     m = match(r"\[done\s*(.*?)ms\]", r)
     @test parse(Float64, m.captures[1]) ≥ 500
 end
+
+
+@testset "refstring" begin
+    @test J.refstring("aa  bb") == "aa-bb"
+    @test J.refstring("aa  bb !") == "aa-bb"
+    @test J.refstring("aa-bb-!") == "aa-bb-"
+    @test J.refstring("aa 🔺 bb") == "aa-bb"
+    @test J.refstring("aaa 0 bb s:2  df") == "aaa-0-bb-s2-df"
+    @test J.refstring("🔺🔺") == string(hash("🔺🔺"))
+    @test J.refstring("blah&#33;") == "blah"
+end
diff --git a/test/runtests.jl b/test/runtests.jl
index 07d4cae8b..5eaab2c79 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -1,4 +1,4 @@
-using JuDoc, Random, Test
+using JuDoc, Test, Markdown
 const J = JuDoc
 const D = joinpath(dirname(dirname(pathof(JuDoc))), "test", "_dummies")