diff --git a/src/lib/Hydra/View/TT.pm b/src/lib/Hydra/View/TT.pm
index c6176231..84fcf3e9 100644
--- a/src/lib/Hydra/View/TT.pm
+++ b/src/lib/Hydra/View/TT.pm
@@ -3,6 +3,7 @@ package Hydra::View::TT;
 use strict;
 use warnings;
 use base 'Catalyst::View::TT';
+use Template::Plugin::HTML;
 use Hydra::Helper::Nix;
 use Time::Seconds;
 
@@ -11,7 +12,20 @@ __PACKAGE__->config(
     ENCODING => 'utf-8',
     PRE_CHOMP => 1,
     POST_CHOMP => 1,
-    expose_methods => [qw/buildLogExists buildStepLogExists jobExists relativeDuration stripSSHUser/]);
+    expose_methods => [qw/
+    buildLogExists
+    buildStepLogExists
+    jobExists
+    linkToJob
+    linkToJobset
+    linkToProject
+    makeNameLinksForJob
+    makeNameLinksForJobset
+    makeNameTextForJob
+    makeNameTextForJobset
+    relativeDuration
+    stripSSHUser
+    /]);
 
 sub buildLogExists {
     my ($self, $c, $build) = @_;
@@ -64,4 +78,250 @@ sub jobExists {
     return defined $jobset->builds->search({ job => $jobName, iscurrent => 1 })->single;
 }
 
+=head2 linkToProject
+
+Given a L<Hydra::Schema::Result::Project>, return a link to the project.
+
+Arguments:
+
+=over 3
+
+=item C<$self>
+=back
+
+=item C<$c>
+Catalyst Context
+=back
+
+=item C<$project>
+
+The L<Hydra::Schema::Result::Project> to link to.
+
+=back
+
+=cut
+sub linkToProject {
+    my ($self, $c, $project) = @_;
+
+    my $html = Template::Plugin::HTML->new();
+
+    my $projectName = $project->name;
+    my $escapedProjectName = $html->escape($projectName);
+
+    return '<a href="' . $c->uri_for('/project', $projectName) . '">' . $escapedProjectName . '</a>';
+}
+
+=head2 linkToJobset
+
+Given a L<Hydra::Schema::Result::Jobset>, return a link to the jobset
+and its project in project:jobset notation.
+
+Arguments:
+
+=over 3
+
+=item C<$self>
+=back
+
+=item C<$c>
+Catalyst Context
+=back
+
+=item C<$jobset>
+
+The L<Hydra::Schema::Result::Jobset> to link to.
+
+=back
+
+=cut
+sub linkToJobset {
+    my ($self, $c, $jobset) = @_;
+
+    my $html = Template::Plugin::HTML->new();
+
+    my $jobsetName = $jobset->name;
+    my $escapedJobsetName = $html->escape($jobsetName);
+
+    return linkToProject($self, $c, $jobset->project) .
+           ':<a href="' . $c->uri_for('/jobset', $jobset->project->name, $jobsetName) . '">' . $escapedJobsetName . '</a>';
+}
+
+=head2 linkToJobset
+
+Given a L<Hydra::Schema::Result::Jobset> and L<String> Job name, return
+a link to the job, jobset, and project in project:jobset:job notation.
+
+Arguments:
+
+=over 4
+
+=item C<$self>
+=back
+
+=item C<$c>
+Catalyst Context
+=back
+
+=item C<$jobset>
+
+The L<Hydra::Schema::Result::Jobset> to link to.
+=back
+
+=item C<$jobName>
+
+The L<String> job name to link to.
+
+=back
+
+=cut
+sub linkToJob {
+    my ($self, $c, $jobset, $jobName) = @_;
+
+    my $html = Template::Plugin::HTML->new();
+
+    my $escapedJobName = $html->escape($jobName);
+
+    return linkToJobset($self, $c, $jobset) .
+           ':<a href="' . $c->uri_for('/job', $jobset->project->name, $jobset->name, $jobName) . '">' . $escapedJobName . '</a>';
+}
+
+=head2 makeNameLinksForJobset
+
+Given a L<Hydra::Schema::Result::Jobset>, return a link to the jobset's
+project and a non-link to the jobset in project:jobset notation.
+
+Arguments:
+
+=over 3
+
+=item C<$self>
+=back
+
+=item C<$c>
+Catalyst Context
+=back
+
+=item C<$jobset>
+
+The L<Hydra::Schema::Result::Jobset> to link to.
+
+=back
+
+=cut
+sub makeNameLinksForJobset {
+    my ($self, $c, $jobset) = @_;
+
+    my $html = Template::Plugin::HTML->new();
+
+    my $escapedJobsetName = $html->escape($jobset->name);
+
+    return linkToProject($self, $c, $jobset->project) . ':' . $escapedJobsetName;
+}
+
+=head2 makeNameLinksForJob
+
+Given a L<Hydra::Schema::Result::Jobset> and L<String> Job name, return
+a link to the jobset and project, and a non-link to the job in
+project:jobset:job notation.
+
+Arguments:
+
+=over 4
+
+=item C<$self>
+=back
+
+=item C<$c>
+Catalyst Context
+=back
+
+=item C<$jobset>
+
+The L<Hydra::Schema::Result::Jobset> to link to.
+
+=back
+
+
+=item C<$jobName>
+
+The L<String> job name to link to.
+
+=back
+
+=cut
+sub makeNameLinksForJob {
+    my ($self, $c, $jobset, $jobName) = @_;
+
+    my $html = Template::Plugin::HTML->new();
+
+    my $escapedJobName = $html->escape($jobName);
+
+    return linkToJobset($self, $c, $jobset) . ':' . $escapedJobName;
+}
+
+=head2 makeNameTextForJobset
+
+Given a L<Hydra::Schema::Result::Jobset>, return the project and
+jobset in project:jobset notation.
+
+Arguments:
+
+=over 3
+
+=item C<$self>
+=back
+
+=item C<$c>
+Catalyst Context
+=back
+
+=item C<$jobset>
+
+The L<Hydra::Schema::Result::Jobset> to link to.
+
+=back
+
+=cut
+sub makeNameTextForJobset {
+    my ($self, $c, $jobset) = @_;
+
+    return $jobset->project->name . ":" . $jobset->name;
+}
+
+=head2 makeNameTextForJob
+
+Given a L<Hydra::Schema::Result::Jobset> and L<String> Job name, return
+the job, jobset, and project in project:jobset:job notation.
+
+Arguments:
+
+=over 4
+
+=item C<$self>
+=back
+
+=item C<$c>
+Catalyst Context
+=back
+
+=item C<$jobset>
+
+The L<Hydra::Schema::Result::Jobset> to link to.
+
+=back
+
+
+=item C<$jobName>
+
+The L<String> job name to link to.
+
+=back
+
+=cut
+sub makeNameTextForJob {
+    my ($self, $c, $jobset, $jobName) = @_;
+
+    return $jobset->project->name . ":" . $jobset->name . ":" . $jobName;
+}
+
 1;
diff --git a/src/root/all.tt b/src/root/all.tt
index a4517349..e877f5b5 100644
--- a/src/root/all.tt
+++ b/src/root/all.tt
@@ -1,7 +1,14 @@
-[% WRAPPER layout.tt title="Latest builds" _
-    (job ? " for job $project.name:$jobset.name:$job" :
-     jobset ? " for jobset $project.name:$jobset.name" :
-     project ? " for project $project.name" : "") %]
+[% WRAPPER layout.tt
+titleHTML="Latest builds" _
+    (job ? " for job " _ linkToJob(jobset, job) :
+     jobset ? " for jobset " _ linkToJobset(jobset) :
+     project ? " for project " _ linkToProject(project) :
+     "")
+  title="Latest builds" _
+    (job ? " for job " _ makeNameTextForJob(jobset, job) :
+     jobset ? " for jobset " _ makeNameTextForJobset(jobset) :
+     project ? " for project $project.name" :
+     "") %]
 [% PROCESS common.tt %]
 
 <p>Showing builds [% (page - 1) * resultsPerPage + 1 %] - [% (page - 1) * resultsPerPage + builds.size %] out of [% total %] in order of descending finish time.</p>
diff --git a/src/root/build.tt b/src/root/build.tt
index f9f442f6..ea3b75c4 100644
--- a/src/root/build.tt
+++ b/src/root/build.tt
@@ -1,4 +1,7 @@
-[% WRAPPER layout.tt title="Build $id of job $project.name:$jobset.name:$job" %]
+[% WRAPPER layout.tt
+  title="Build $id of job " _ makeNameTextForJob(jobset, job)
+  titleHTML="Build $id of job " _ linkToJob(jobset, job)
+%]
 [% PROCESS common.tt %]
 [% PROCESS "product-list.tt" %]
 [% USE HTML %]
diff --git a/src/root/edit-jobset.tt b/src/root/edit-jobset.tt
index 22874815..dbd26dcc 100644
--- a/src/root/edit-jobset.tt
+++ b/src/root/edit-jobset.tt
@@ -1,8 +1,8 @@
 [% WRAPPER layout.tt title=
     (create ? "Creating jobset in project $project.name" :
-     createFromEval ? "Creating jobset from evaluation $eval.id of $project.name:$jobset.name" :
-     cloneJobset ? "Cloning jobset $project.name:$jobset.name" :
-     "Editing jobset $project.name:$jobset.name") %]
+     createFromEval ? "Creating jobset from evaluation $eval.id of " _ makeNameTextForJobset(jobset) :
+     cloneJobset ? "Cloning jobset " _ makeNameTextForJobset(jobset) :
+     "Editing jobset " _ makeNameTextForJobset(jobset)) %]
 [% PROCESS common.tt %]
 [% USE format %]
 
diff --git a/src/root/evals.tt b/src/root/evals.tt
index 977441b3..c12079d1 100644
--- a/src/root/evals.tt
+++ b/src/root/evals.tt
@@ -1,6 +1,11 @@
-[% WRAPPER layout.tt title=
+[% WRAPPER layout.tt
+  title=
     (build ? "Evaluations containing build $build.id" :
-     jobset ? "Evaluations of jobset $project.name:$jobset.name" :
+     jobset ? "Evaluations of jobset " _ makeNameTextForJobset(jobset) :
+     "Latest evaluations")
+  titleHTML =
+    (build ? "Evaluations containing build $build.id" :
+     jobset ? "Evaluations of jobset " _ linkToJobset(jobset) :
      "Latest evaluations") %]
 [% PROCESS common.tt %]
 
diff --git a/src/root/job.tt b/src/root/job.tt
index e8a56569..7e475f69 100644
--- a/src/root/job.tt
+++ b/src/root/job.tt
@@ -1,5 +1,6 @@
 [% WRAPPER layout.tt
-    title="Job $project.name:$jobset.name:$job"
+    title=makeNameTextForJob(jobset, job)
+    titleHTML=makeNameLinksForJob(jobset, job)
     starUri=c.uri_for(c.controller('Job').action_for('star'), c.req.captures)
 %]
 [% PROCESS common.tt %]
diff --git a/src/root/jobset-eval.tt b/src/root/jobset-eval.tt
index d6f2b6d1..e1d25af1 100644
--- a/src/root/jobset-eval.tt
+++ b/src/root/jobset-eval.tt
@@ -1,4 +1,6 @@
-[% WRAPPER layout.tt title="Evaluation $eval.id of jobset $project.name:$jobset.name " %]
+[% WRAPPER layout.tt
+  title="Evaluation $eval.id of jobset " _ makeNameTextForJobset(jobset)
+  titleHTML="Evaluation $eval.id of jobset " _ linkToJobset(jobset) %]
 [% PROCESS common.tt %]
 
 <div class="dropdown">
diff --git a/src/root/jobset.tt b/src/root/jobset.tt
index fa85c31c..55aaf094 100644
--- a/src/root/jobset.tt
+++ b/src/root/jobset.tt
@@ -1,4 +1,6 @@
-[% WRAPPER layout.tt title="Jobset $project.name:$jobset.name" %]
+[% WRAPPER layout.tt
+  titleHTML=makeNameLinksForJobset(jobset)
+  title=makeNameTextForJobset(jobset) %]
 [% PROCESS common.tt %]
 [% USE format %]
 
diff --git a/src/root/layout.tt b/src/root/layout.tt
index e5331ddd..8eb1f119 100644
--- a/src/root/layout.tt
+++ b/src/root/layout.tt
@@ -81,7 +81,8 @@
 
       [% IF !hideHeader %]
         <div class="page-header">
-          [% IF c.user_exists && starUri; INCLUDE makeStar; " "; END; HTML.escape(title) %]
+          [% IF c.user_exists && starUri; INCLUDE makeStar; " "; END %]
+          [% IF titleHTML != ""; titleHTML; ELSE; HTML.escape(title); END %]
         </div>
       [% ELSE %]
         [% IF first %]<br />[% first = 0; END; %]
diff --git a/src/root/log.tt b/src/root/log.tt
index fcc07d97..8bb4954d 100644
--- a/src/root/log.tt
+++ b/src/root/log.tt
@@ -1,4 +1,7 @@
-[% WRAPPER layout.tt title="Log of " _ (step ? " step $step.stepnr of " : "") _ "build ${build.id} of job $build.project.name:$build.jobset.name:$build.job.name" %]
+[% WRAPPER layout.tt
+  titleHTML="Log of " _ (step ? " step $step.stepnr of " : "") _ "build ${build.id} of job " _ linkToJob(build.jobset, job)
+  title="Log of " _ (step ? " step $step.stepnr of " : "") _ "build ${build.id} of job " _ makeNameTextForJob(build.jobset, job)
+%]
 [% PROCESS common.tt %]
 
 <p>
diff --git a/t/View/TT.t b/t/View/TT.t
new file mode 100644
index 00000000..118654fc
--- /dev/null
+++ b/t/View/TT.t
@@ -0,0 +1,77 @@
+use feature 'unicode_strings';
+use strict;
+use warnings;
+use Setup;
+
+my %ctx = test_init();
+
+require Hydra::Schema;
+require Hydra::Model::DB;
+
+use Test2::V0;
+
+require Hydra; # calls setup()
+
+
+my $db = Hydra::Model::DB->new;
+hydra_setup($db);
+
+require Hydra::View::TT;
+
+# The following lines are a cheap and hacky trick to get $c,
+# there is no other reason to call /.
+require Catalyst::Test;
+Catalyst::Test->import('Hydra');
+my($_, $c) = ctx_request('/');
+
+
+my $project = $db->resultset('Projects')->create({name => "tests", displayname => "", owner => "root"});
+my $jobset = createBaseJobset("example", "bogus.nix", $ctx{jobsdir});
+my $job = "myjob";
+
+
+is(
+    Hydra::View::TT::linkToProject(undef, $c, $project),
+    '<a href="http://localhost/project/tests">tests</a>',
+    "linkToProject"
+);
+is(
+    Hydra::View::TT::linkToJobset(undef, $c, $jobset),
+    '<a href="http://localhost/project/tests">tests</a>'
+    . ':<a href="http://localhost/jobset/tests/example">example</a>',
+    "linkToJobset"
+);
+is(
+    Hydra::View::TT::linkToJob(undef, $c, $jobset, $job),
+    '<a href="http://localhost/project/tests">tests</a>'
+    . ':<a href="http://localhost/jobset/tests/example">example</a>'
+    . ':<a href="http://localhost/job/tests/example/myjob">myjob</a>',
+    "linkToJob"
+);
+
+is(
+    Hydra::View::TT::makeNameLinksForJobset(undef, $c, $jobset),
+    '<a href="http://localhost/project/tests">tests</a>'
+    . ':example',
+    "makeNameLinksForJobset"
+);
+is(
+    Hydra::View::TT::makeNameLinksForJob(undef, $c, $jobset, $job),
+    '<a href="http://localhost/project/tests">tests</a>'
+    . ':<a href="http://localhost/jobset/tests/example">example</a>'
+    . ':myjob',
+    "makeNameLinksForJob"
+);
+
+is(
+    Hydra::View::TT::makeNameTextForJobset(undef, $c, $jobset),
+    'tests:example',
+    "makeNameTextForJobset"
+);
+is(
+    Hydra::View::TT::makeNameTextForJob(undef, $c, $jobset, $job),
+    'tests:example:myjob',
+    "makeNameTextForJob"
+);
+
+done_testing;