diff --git a/app/assets/javascripts/code_navigation/components/app.vue b/app/assets/javascripts/code_navigation/components/app.vue
index d38b38947b6bd15cc5d9e59103b15957227ce153..5c77f087d63d344ce265caf267c136d6d6900677 100644
--- a/app/assets/javascripts/code_navigation/components/app.vue
+++ b/app/assets/javascripts/code_navigation/components/app.vue
@@ -7,6 +7,23 @@ export default {
   components: {
     Popover,
   },
+  props: {
+    codeNavigationPath: {
+      type: String,
+      required: false,
+      default: null,
+    },
+    blobPath: {
+      type: String,
+      required: false,
+      default: null,
+    },
+    pathPrefix: {
+      type: String,
+      required: false,
+      default: null,
+    },
+  },
   computed: {
     ...mapState([
       'currentDefinition',
@@ -16,6 +33,14 @@ export default {
     ]),
   },
   mounted() {
+    if (this.codeNavigationPath && this.blobPath && this.pathPrefix) {
+      const initialData = {
+        blobs: [{ path: this.blobPath, codeNavigationPath: this.codeNavigationPath }],
+        definitionPathPrefix: this.pathPrefix,
+      };
+      this.setInitialData(initialData);
+    }
+
     this.body = document.body;
 
     eventHub.$on('showBlobInteractionZones', this.showBlobInteractionZones);
@@ -28,7 +53,7 @@ export default {
     this.removeGlobalEventListeners();
   },
   methods: {
-    ...mapActions(['fetchData', 'showDefinition', 'showBlobInteractionZones']),
+    ...mapActions(['fetchData', 'showDefinition', 'showBlobInteractionZones', 'setInitialData']),
     addGlobalEventListeners() {
       if (this.body) {
         this.body.addEventListener('click', this.showDefinition);
diff --git a/app/assets/javascripts/pages/projects/blob/show/index.js b/app/assets/javascripts/pages/projects/blob/show/index.js
index 2fc9a1114051b601b0a100b6b29f850eb843f031..740fdb8a96a61965e29d0a8ac6ffe91dc185b727 100644
--- a/app/assets/javascripts/pages/projects/blob/show/index.js
+++ b/app/assets/javascripts/pages/projects/blob/show/index.js
@@ -1,4 +1,5 @@
 import Vue from 'vue';
+import Vuex from 'vuex';
 import VueApollo from 'vue-apollo';
 import VueRouter from 'vue-router';
 import TableOfContents from '~/blob/components/table_contents.vue';
@@ -11,7 +12,9 @@ import initWebIdeLink from '~/pages/projects/shared/web_ide_link';
 import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
 import BlobContentViewer from '~/repository/components/blob_content_viewer.vue';
 import '~/sourcegraph/load';
+import createStore from '~/code_navigation/store';
 
+Vue.use(Vuex);
 Vue.use(VueApollo);
 Vue.use(VueRouter);
 
@@ -29,6 +32,7 @@ if (viewBlobEl) {
   // eslint-disable-next-line no-new
   new Vue({
     el: viewBlobEl,
+    store: createStore(),
     router,
     apolloProvider,
     provide: {
@@ -78,7 +82,7 @@ GpgBadges.fetch();
 
 const codeNavEl = document.getElementById('js-code-navigation');
 
-if (codeNavEl) {
+if (codeNavEl && !viewBlobEl) {
   const { codeNavigationPath, blobPath, definitionPathPrefix } = codeNavEl.dataset;
 
   // eslint-disable-next-line promise/catch-or-return
diff --git a/app/assets/javascripts/repository/components/blob_content_viewer.vue b/app/assets/javascripts/repository/components/blob_content_viewer.vue
index 799c4affa527862556c5c081ed73d2a23d528caf..3fdc45b4e3de080ac079d5a6d9b1164521d69859 100644
--- a/app/assets/javascripts/repository/components/blob_content_viewer.vue
+++ b/app/assets/javascripts/repository/components/blob_content_viewer.vue
@@ -11,6 +11,7 @@ import { __ } from '~/locale';
 import { redirectTo } from '~/lib/utils/url_utility';
 import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
 import WebIdeLink from '~/vue_shared/components/web_ide_link.vue';
+import CodeIntelligence from '~/code_navigation/components/app.vue';
 import getRefMixin from '../mixins/get_ref';
 import blobInfoQuery from '../queries/blob_info.query.graphql';
 import { DEFAULT_BLOB_INFO, TEXT_FILE_TYPE, LFS_STORAGE } from '../constants';
@@ -30,6 +31,7 @@ export default {
     GlButton,
     ForkSuggestion,
     WebIdeLink,
+    CodeIntelligence,
   },
   mixins: [getRefMixin, glFeatureFlagMixin()],
   inject: {
@@ -274,6 +276,12 @@ export default {
         :loading="isLoadingLegacyViewer"
       />
       <component :is="blobViewer" v-else :blob="blobInfo" class="blob-viewer" />
+      <code-intelligence
+        v-if="blobViewer || legacyViewerLoaded"
+        :code-navigation-path="blobInfo.codeNavigationPath"
+        :blob-path="blobInfo.path"
+        :path-prefix="blobInfo.projectBlobPathRoot"
+      />
     </div>
   </div>
 </template>
diff --git a/app/assets/javascripts/repository/constants.js b/app/assets/javascripts/repository/constants.js
index aa2f2fa8f11757b5f59ffe4ea2ce1ecb7e0c00a7..d71f23c57dded5e71322e07fe7c0671673591cbe 100644
--- a/app/assets/javascripts/repository/constants.js
+++ b/app/assets/javascripts/repository/constants.js
@@ -52,6 +52,8 @@ export const DEFAULT_BLOB_INFO = {
           ideEditPath: '',
           forkAndEditPath: '',
           ideForkAndEditPath: '',
+          codeNavigationPath: '',
+          projectBlobPathRoot: '',
           forkAndViewPath: '',
           storedExternally: false,
           externalStorage: '',
diff --git a/app/assets/javascripts/repository/index.js b/app/assets/javascripts/repository/index.js
index 120c32caefd36dfdae63a7de4b4257b8d54dc8ed..b38a1cfdc7b6844e921c74887ccb1c5396e445b1 100644
--- a/app/assets/javascripts/repository/index.js
+++ b/app/assets/javascripts/repository/index.js
@@ -1,10 +1,12 @@
 import { GlButton } from '@gitlab/ui';
 import Vue from 'vue';
+import Vuex from 'vuex';
 import { parseBoolean } from '~/lib/utils/common_utils';
 import { escapeFileUrl } from '~/lib/utils/url_utility';
 import { __ } from '~/locale';
 import initWebIdeLink from '~/pages/projects/shared/web_ide_link';
 import PerformancePlugin from '~/performance/vue_performance_plugin';
+import createStore from '~/code_navigation/store';
 import App from './components/app.vue';
 import Breadcrumbs from './components/breadcrumbs.vue';
 import DirectoryDownloadLinks from './components/directory_download_links.vue';
@@ -19,6 +21,7 @@ import createRouter from './router';
 import { updateFormAction } from './utils/dom';
 import { setTitle } from './utils/title';
 
+Vue.use(Vuex);
 Vue.use(PerformancePlugin, {
   components: ['SimpleViewer', 'BlobContent'],
 });
@@ -200,6 +203,7 @@ export default function setupVueRepositoryList() {
   // eslint-disable-next-line no-new
   new Vue({
     el,
+    store: createStore(),
     router,
     apolloProvider,
     render(h) {
diff --git a/app/assets/javascripts/repository/queries/blob_info.query.graphql b/app/assets/javascripts/repository/queries/blob_info.query.graphql
index 14d7f903e757f97878c2764b18bc8409700896a4..389c3715586379d8a59af4339eab3885dceb0c5b 100644
--- a/app/assets/javascripts/repository/queries/blob_info.query.graphql
+++ b/app/assets/javascripts/repository/queries/blob_info.query.graphql
@@ -31,6 +31,8 @@ query getBlobInfo(
           ideEditPath
           forkAndEditPath
           ideForkAndEditPath
+          codeNavigationPath
+          projectBlobPathRoot
           forkAndViewPath
           environmentFormattedExternalUrl
           environmentExternalUrlForRouteMap
diff --git a/app/graphql/types/repository/blob_type.rb b/app/graphql/types/repository/blob_type.rb
index 1c14cefd7d7924dc15540dcae79ec8fcd43812dd..eb481c6c91d7a507314f88b81b9130852b989e18 100644
--- a/app/graphql/types/repository/blob_type.rb
+++ b/app/graphql/types/repository/blob_type.rb
@@ -134,6 +134,12 @@ class BlobType < BaseObject
             null: true,
             calls_gitaly: true
 
+      field :code_navigation_path, GraphQL::Types::String, null: true, calls_gitaly: true,
+            description: 'Web path for code navigation.'
+
+      field :project_blob_path_root, GraphQL::Types::String, null: true,
+            description: 'Web path for the root of the blob.'
+
       def raw_text_blob
         object.data unless object.binary?
       end
diff --git a/app/presenters/blob_presenter.rb b/app/presenters/blob_presenter.rb
index 70a1ed188f37750eb1d73b36deab33bcccdba4c1..c4a5bfe71e32409f7069a251898aaeec3f8a4080 100644
--- a/app/presenters/blob_presenter.rb
+++ b/app/presenters/blob_presenter.rb
@@ -131,6 +131,14 @@ def external_storage_url
     external_storage_url_or_path(url_helpers.project_raw_url(project, ref_qualified_path), project)
   end
 
+  def code_navigation_path
+    Gitlab::CodeNavigationPath.new(project, blob.commit_id).full_json_path_for(blob.path)
+  end
+
+  def project_blob_path_root
+    project_blob_path(project, blob.commit_id)
+  end
+
   private
 
   def url_helpers
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index aa6c4da6262811dcb00e56e53043eea5ecfe1d00..f8f0eb8b0f5eb34bf4f0a15c3df17fbe0a471808 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -15025,6 +15025,7 @@ Returns [`Tree`](#tree).
 | <a id="repositoryblobblamepath"></a>`blamePath` | [`String`](#string) | Web path to blob blame page. |
 | <a id="repositoryblobcancurrentuserpushtobranch"></a>`canCurrentUserPushToBranch` | [`Boolean`](#boolean) | Whether the current user can push to the branch. |
 | <a id="repositoryblobcanmodifyblob"></a>`canModifyBlob` | [`Boolean`](#boolean) | Whether the current user can modify the blob. |
+| <a id="repositoryblobcodenavigationpath"></a>`codeNavigationPath` | [`String`](#string) | Web path for code navigation. |
 | <a id="repositoryblobcodeowners"></a>`codeOwners` | [`[UserCore!]`](#usercore) | List of code owners for the blob. |
 | <a id="repositoryblobeditblobpath"></a>`editBlobPath` | [`String`](#string) | Web path to edit the blob in the old-style editor. |
 | <a id="repositoryblobenvironmentexternalurlforroutemap"></a>`environmentExternalUrlForRouteMap` | [`String`](#string) | Web path to blob on an environment. |
@@ -15048,6 +15049,7 @@ Returns [`Tree`](#tree).
 | <a id="repositoryblobpermalinkpath"></a>`permalinkPath` | [`String`](#string) | Web path to blob permalink. |
 | <a id="repositoryblobpipelineeditorpath"></a>`pipelineEditorPath` | [`String`](#string) | Web path to edit .gitlab-ci.yml file. |
 | <a id="repositoryblobplaindata"></a>`plainData` | [`String`](#string) | Blob plain highlighted data. |
+| <a id="repositoryblobprojectblobpathroot"></a>`projectBlobPathRoot` | [`String`](#string) | Web path for the root of the blob. |
 | <a id="repositoryblobrawblob"></a>`rawBlob` | [`String`](#string) | Raw content of the blob. |
 | <a id="repositoryblobrawpath"></a>`rawPath` | [`String`](#string) | Web path to download the raw blob. |
 | <a id="repositoryblobrawsize"></a>`rawSize` | [`Int`](#int) | Size (in bytes) of the blob, or the blob target if stored externally. |
diff --git a/ee/spec/frontend/repository/components/blob_content_viewer_spec.js b/ee/spec/frontend/repository/components/blob_content_viewer_spec.js
index 6b0eb09d180bcafade06f4a87b79690b756c560f..90b59a7a6c868abaf24d3cb4e42ae3d76eb01636 100644
--- a/ee/spec/frontend/repository/components/blob_content_viewer_spec.js
+++ b/ee/spec/frontend/repository/components/blob_content_viewer_spec.js
@@ -1,4 +1,5 @@
 import Vue from 'vue';
+import Vuex from 'vuex';
 import VueRouter from 'vue-router';
 import VueApollo from 'vue-apollo';
 import axios from 'axios';
@@ -28,6 +29,9 @@ let mockResolver;
 
 Vue.use(VueApollo);
 
+const createMockStore = () =>
+  new Vuex.Store({ actions: { fetchData: jest.fn, setInitialData: jest.fn() } });
+
 const createComponent = async (mockData = {}) => {
   const {
     blob = simpleViewerMock,
@@ -58,6 +62,7 @@ const createComponent = async (mockData = {}) => {
   const fakeApollo = createMockApollo([[blobInfoQuery, mockResolver]]);
 
   wrapper = mountExtended(BlobContentViewer, {
+    store: createMockStore(),
     router,
     apolloProvider: fakeApollo,
     propsData: {
diff --git a/spec/frontend/code_navigation/components/app_spec.js b/spec/frontend/code_navigation/components/app_spec.js
index 9306c15e6767c44e88b066b0d34fb8bad3408a87..0d7c0360e9bb128794c02f4efeec2b6c255eeeb1 100644
--- a/spec/frontend/code_navigation/components/app_spec.js
+++ b/spec/frontend/code_navigation/components/app_spec.js
@@ -5,13 +5,14 @@ import App from '~/code_navigation/components/app.vue';
 import Popover from '~/code_navigation/components/popover.vue';
 import createState from '~/code_navigation/store/state';
 
+const setInitialData = jest.fn();
 const fetchData = jest.fn();
 const showDefinition = jest.fn();
 let wrapper;
 
 Vue.use(Vuex);
 
-function factory(initialState = {}) {
+function factory(initialState = {}, props = {}) {
   const store = new Vuex.Store({
     state: {
       ...createState(),
@@ -19,12 +20,13 @@ function factory(initialState = {}) {
       definitionPathPrefix: 'https://test.com/blob/main',
     },
     actions: {
+      setInitialData,
       fetchData,
       showDefinition,
     },
   });
 
-  wrapper = shallowMount(App, { store });
+  wrapper = shallowMount(App, { store, propsData: { ...props } });
 }
 
 describe('Code navigation app component', () => {
@@ -32,6 +34,19 @@ describe('Code navigation app component', () => {
     wrapper.destroy();
   });
 
+  it('sets initial data on mount if the correct props are passed', () => {
+    const codeNavigationPath = 'code/nav/path.js';
+    const path = 'blob/path.js';
+    const definitionPathPrefix = 'path/prefix';
+
+    factory({}, { codeNavigationPath, blobPath: path, pathPrefix: definitionPathPrefix });
+
+    expect(setInitialData).toHaveBeenCalledWith(expect.anything(), {
+      blobs: [{ codeNavigationPath, path }],
+      definitionPathPrefix,
+    });
+  });
+
   it('fetches data on mount', () => {
     factory();
 
diff --git a/spec/frontend/repository/components/blob_content_viewer_spec.js b/spec/frontend/repository/components/blob_content_viewer_spec.js
index 094892b87b21f76c6b2565e984f043ff5c9568db..54ec4e8e579ec0f37cfdb678d8d99f510d83628a 100644
--- a/spec/frontend/repository/components/blob_content_viewer_spec.js
+++ b/spec/frontend/repository/components/blob_content_viewer_spec.js
@@ -1,5 +1,6 @@
 import { GlLoadingIcon } from '@gitlab/ui';
 import { mount, shallowMount } from '@vue/test-utils';
+import Vuex from 'vuex';
 import Vue, { nextTick } from 'vue';
 import axios from 'axios';
 import MockAdapter from 'axios-mock-adapter';
@@ -17,9 +18,11 @@ import DownloadViewer from '~/repository/components/blob_viewers/download_viewer
 import EmptyViewer from '~/repository/components/blob_viewers/empty_viewer.vue';
 import SourceViewer from '~/vue_shared/components/source_viewer/source_viewer.vue';
 import blobInfoQuery from '~/repository/queries/blob_info.query.graphql';
+import CodeIntelligence from '~/code_navigation/components/app.vue';
 import { redirectTo } from '~/lib/utils/url_utility';
 import { isLoggedIn } from '~/lib/utils/common_utils';
 import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import httpStatusCodes from '~/lib/utils/http_status';
 import {
   simpleViewerMock,
   richViewerMock,
@@ -38,6 +41,9 @@ let mockResolver;
 
 const mockAxios = new MockAdapter(axios);
 
+const createMockStore = () =>
+  new Vuex.Store({ actions: { fetchData: jest.fn, setInitialData: jest.fn() } });
+
 const createComponent = async (mockData = {}, mountFn = shallowMount) => {
   Vue.use(VueApollo);
 
@@ -75,6 +81,7 @@ const createComponent = async (mockData = {}, mountFn = shallowMount) => {
 
   wrapper = extendedWrapper(
     mountFn(BlobContentViewer, {
+      store: createMockStore(),
       apolloProvider: fakeApollo,
       propsData: propsMock,
       mixins: [{ data: () => ({ ref: refMock }) }],
@@ -104,6 +111,7 @@ describe('Blob content viewer component', () => {
   const findBlobContent = () => wrapper.findComponent(BlobContent);
   const findBlobButtonGroup = () => wrapper.findComponent(BlobButtonGroup);
   const findForkSuggestion = () => wrapper.findComponent(ForkSuggestion);
+  const findCodeIntelligence = () => wrapper.findComponent(CodeIntelligence);
 
   beforeEach(() => {
     isLoggedIn.mockReturnValue(true);
@@ -219,6 +227,26 @@ describe('Blob content viewer component', () => {
       loadViewer.mockRestore();
     });
 
+    it('renders a CodeIntelligence component with the correct props', async () => {
+      loadViewer.mockReturnValue(SourceViewer);
+
+      await createComponent();
+
+      expect(findCodeIntelligence().props()).toMatchObject({
+        codeNavigationPath: simpleViewerMock.codeNavigationPath,
+        blobPath: simpleViewerMock.path,
+        pathPrefix: simpleViewerMock.projectBlobPathRoot,
+      });
+    });
+
+    it('does not load a CodeIntelligence component when no viewers are loaded', async () => {
+      const url = 'some_file.js?format=json&viewer=rich';
+      mockAxios.onGet(url).replyOnce(httpStatusCodes.INTERNAL_SERVER_ERROR);
+      await createComponent({ blob: { ...richViewerMock, fileType: 'unknown' } });
+
+      expect(findCodeIntelligence().exists()).toBe(false);
+    });
+
     it('does not render a BlobContent component if a Blob viewer is available', async () => {
       loadViewer.mockReturnValue(() => true);
       await createComponent({ blob: richViewerMock });
diff --git a/spec/frontend/repository/mock_data.js b/spec/frontend/repository/mock_data.js
index ba517fbc20c713893bd5748ae863d420568b6650..b47aefd190513bb32561abad183d83d03e0631c3 100644
--- a/spec/frontend/repository/mock_data.js
+++ b/spec/frontend/repository/mock_data.js
@@ -13,6 +13,8 @@ export const simpleViewerMock = {
   forkAndEditPath: 'some_file.js/fork/edit',
   ideForkAndEditPath: 'some_file.js/fork/ide',
   forkAndViewPath: 'some_file.js/fork/view',
+  codeNavigationPath: '',
+  projectBlobPathRoot: '',
   environmentFormattedExternalUrl: '',
   environmentExternalUrlForRouteMap: '',
   canModifyBlob: true,
diff --git a/spec/graphql/types/repository/blob_type_spec.rb b/spec/graphql/types/repository/blob_type_spec.rb
index 66ab4ef7170353e8c6aff6a98e285cf426a54823..e721b8b5118493fe09b041e0201b0562db4ca30d 100644
--- a/spec/graphql/types/repository/blob_type_spec.rb
+++ b/spec/graphql/types/repository/blob_type_spec.rb
@@ -31,6 +31,8 @@
       :permalink_path,
       :environment_formatted_external_url,
       :environment_external_url_for_route_map,
+      :code_navigation_path,
+      :project_blob_path_root,
       :code_owners,
       :simple_viewer,
       :rich_viewer,
diff --git a/spec/presenters/blob_presenter_spec.rb b/spec/presenters/blob_presenter_spec.rb
index 847668ffc52f157e81a72172576130d46de46aa7..21bb96c061b5b35444f1d6c7cd147c4dcfab8d46 100644
--- a/spec/presenters/blob_presenter_spec.rb
+++ b/spec/presenters/blob_presenter_spec.rb
@@ -154,6 +154,16 @@
     end
   end
 
+  describe '#code_navigation_path' do
+    let(:code_navigation_path) { Gitlab::CodeNavigationPath.new(project, blob.commit_id).full_json_path_for(blob.path) }
+
+    it { expect(presenter.code_navigation_path).to eq(code_navigation_path) }
+  end
+
+  describe '#project_blob_path_root' do
+    it { expect(presenter.project_blob_path_root).to eq("/#{project.full_path}/-/blob/HEAD") }
+  end
+
   context 'given a Gitlab::Graphql::Representation::TreeEntry' do
     let(:blob) { Gitlab::Graphql::Representation::TreeEntry.new(super(), repository) }