diff --git a/app/assets/javascripts/merge_request_dashboard/components/app.vue b/app/assets/javascripts/merge_request_dashboard/components/app.vue
new file mode 100644
index 0000000000000000000000000000000000000000..2d95c1fd126140bd28ce06e933760305ee6c714f
--- /dev/null
+++ b/app/assets/javascripts/merge_request_dashboard/components/app.vue
@@ -0,0 +1,18 @@
+<script>
+import { GlLoadingIcon } from '@gitlab/ui';
+
+export default {
+  components: {
+    GlLoadingIcon,
+  },
+};
+</script>
+
+<template>
+  <div>
+    <div class="page-title-holder">
+      <h1 class="page-title gl-font-size-h-display">{{ __('Merge Requests') }}</h1>
+    </div>
+    <gl-loading-icon size="lg" />
+  </div>
+</template>
diff --git a/app/assets/javascripts/merge_request_dashboard/index.js b/app/assets/javascripts/merge_request_dashboard/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..799462844e618ae2c704a53336c75118a63e4d8c
--- /dev/null
+++ b/app/assets/javascripts/merge_request_dashboard/index.js
@@ -0,0 +1,18 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+import App from './components/app.vue';
+
+export function initMergeRequestDashboard(el) {
+  Vue.use(VueApollo);
+
+  return new Vue({
+    el,
+    apolloProvider: new VueApollo({
+      defaultClient: createDefaultClient(),
+    }),
+    render(createElement) {
+      return createElement(App);
+    },
+  });
+}
diff --git a/app/assets/javascripts/pages/dashboard/merge_requests/index.js b/app/assets/javascripts/pages/dashboard/merge_requests/index.js
index 774e234a358cf9c7ad91ae83fbe6a2ca29d8e5c7..9f785d561742c6f315378fd0e95dfb363d3c95af 100644
--- a/app/assets/javascripts/pages/dashboard/merge_requests/index.js
+++ b/app/assets/javascripts/pages/dashboard/merge_requests/index.js
@@ -6,23 +6,33 @@ import { initNewResourceDropdown } from '~/vue_shared/components/new_resource_dr
 import { RESOURCE_TYPE_MERGE_REQUEST } from '~/vue_shared/components/new_resource_dropdown/constants';
 import searchUserProjectsWithMergeRequestsEnabled from '~/vue_shared/components/new_resource_dropdown/graphql/search_user_projects_with_merge_requests_enabled.query.graphql';
 
-const IssuableFilteredSearchTokenKeys = createFilteredSearchTokenKeys({
-  disableReleaseFilter: true,
-});
+const el = document.getElementById('js-merge-request-dashboard');
 
-addExtraTokensForMergeRequests(IssuableFilteredSearchTokenKeys, {
-  disableBranchFilter: true,
-  disableReleaseFilter: true,
-  disableEnvironmentFilter: true,
-});
+if (el) {
+  requestIdleCallback(async () => {
+    const { initMergeRequestDashboard } = await import('~/merge_request_dashboard');
 
-initFilteredSearch({
-  page: FILTERED_SEARCH.MERGE_REQUESTS,
-  filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys,
-  useDefaultState: true,
-});
+    initMergeRequestDashboard(el);
+  });
+} else {
+  const IssuableFilteredSearchTokenKeys = createFilteredSearchTokenKeys({
+    disableReleaseFilter: true,
+  });
 
-initNewResourceDropdown({
-  resourceType: RESOURCE_TYPE_MERGE_REQUEST,
-  query: searchUserProjectsWithMergeRequestsEnabled,
-});
+  addExtraTokensForMergeRequests(IssuableFilteredSearchTokenKeys, {
+    disableBranchFilter: true,
+    disableReleaseFilter: true,
+    disableEnvironmentFilter: true,
+  });
+
+  initFilteredSearch({
+    page: FILTERED_SEARCH.MERGE_REQUESTS,
+    filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys,
+    useDefaultState: true,
+  });
+
+  initNewResourceDropdown({
+    resourceType: RESOURCE_TYPE_MERGE_REQUEST,
+    query: searchUserProjectsWithMergeRequestsEnabled,
+  });
+}
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index ef160fdc36b9d2bb80f97f28a898881a2b346216..0ed504ed61aee096d43acf4906e283df8ae1aa7c 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -328,6 +328,10 @@ def project_merge_requests_list_data(project, current_user)
       is_signed_in: current_user.present?.to_s
     }
   end
+
+  def merge_request_dashboard_enabled?(current_user)
+    Feature.enabled?(:merge_request_dashboard, current_user, type: :wip)
+  end
 end
 
 MergeRequestsHelper.prepend_mod_with('MergeRequestsHelper')
diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml
index d29cb56db0718f51750195902a7f9ad28520aa19..5d5ba8ac605380c6f124538aca939edf24ed9232 100644
--- a/app/views/dashboard/merge_requests.html.haml
+++ b/app/views/dashboard/merge_requests.html.haml
@@ -1,5 +1,7 @@
 :ruby
-  title = if params[:reviewer_username] == current_user.username
+  title = if merge_request_dashboard_enabled?(current_user)
+            _('Merge Requests')
+          elsif params[:reviewer_username] == current_user.username
             _("Review requests")
           elsif params[:assignee_username] == current_user.username
             _("Assigned merge requests")
@@ -14,25 +16,31 @@
 = render_if_exists 'shared/dashboard/saml_reauth_notice',
   groups_requiring_saml_reauth: user_groups_requiring_reauth
 
-.page-title-holder.d-flex.align-items-start.flex-column.flex-sm-row.align-items-sm-center
-  %h1.page-title.gl-font-size-h-display= title
+- if merge_request_dashboard_enabled?(current_user)
+  #js-merge-request-dashboard
+    .page-title-holder
+      %h1.page-title.gl-font-size-h-display= _('Merge Requests')
+    = gl_loading_icon(size: 'lg')
+- else
+  .page-title-holder.d-flex.align-items-start.flex-column.flex-sm-row.align-items-sm-center
+    %h1.page-title.gl-font-size-h-display= title
 
-  - if current_user
-    .page-title-controls.ml-0.mb-3.ml-sm-auto.mb-sm-0
-      = render 'shared/new_project_item_vue_select'
+    - if current_user
+      .page-title-controls.ml-0.mb-3.ml-sm-auto.mb-sm-0
+        = render 'shared/new_project_item_vue_select'
 
-.top-area
-  = render 'shared/issuable/nav', type: :merge_requests, display_count: !(@no_filters_set || @search_timeout_occurred)
+  .top-area
+    = render 'shared/issuable/nav', type: :merge_requests, display_count: !(@no_filters_set || @search_timeout_occurred)
 
-= render 'shared/issuable/search_bar',
-  type: :merge_requests,
-  disable_target_branch: true,
-  disable_releases: true,
-  disable_environments: true
+  = render 'shared/issuable/search_bar',
+    type: :merge_requests,
+    disable_target_branch: true,
+    disable_releases: true,
+    disable_environments: true
 
-- if current_user && @no_filters_set
-  = render 'no_filter_selected'
-- elsif @search_timeout_occurred
-  = render 'shared/dashboard/search_timeout_occurred'
-- else
-  = render 'shared/merge_requests'
+  - if current_user && @no_filters_set
+    = render 'no_filter_selected'
+  - elsif @search_timeout_occurred
+    = render 'shared/dashboard/search_timeout_occurred'
+  - else
+    = render 'shared/merge_requests'
diff --git a/config/feature_flags/wip/merge_request_dashboard.yml b/config/feature_flags/wip/merge_request_dashboard.yml
new file mode 100644
index 0000000000000000000000000000000000000000..cea0775417e4e317cf59ad6b2b33ea47f5fa8a75
--- /dev/null
+++ b/config/feature_flags/wip/merge_request_dashboard.yml
@@ -0,0 +1,9 @@
+---
+name: merge_request_dashboard
+feature_issue_url: https://gitlab.com/groups/gitlab-org/-/epics/13448
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/150661
+rollout_issue_url:
+milestone: '17.0'
+group: group::code review
+type: wip
+default_enabled: false
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 77b95044318b631e5b90702acf4cc48544ac273d..1e47810846810be8544996ec1db69737cf8371c4 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -336,6 +336,9 @@
       # Disable license requirement for duo chat (self managed), which is subject to change.
       # See https://gitlab.com/gitlab-org/gitlab/-/issues/457283
       stub_feature_flags(duo_chat_requires_licensed_seat_sm: false)
+
+      # Experimental merge request dashboard
+      stub_feature_flags(merge_request_dashboard: false)
     else
       unstub_all_feature_flags
     end