diff --git a/src/core/public/application/integration_tests/router.test.tsx b/src/core/public/application/integration_tests/router.test.tsx
index ffc10820a9c37..10544c348afb0 100644
--- a/src/core/public/application/integration_tests/router.test.tsx
+++ b/src/core/public/application/integration_tests/router.test.tsx
@@ -18,7 +18,7 @@
  */
 
 import React from 'react';
-import { createMemoryHistory, History } from 'history';
+import { createMemoryHistory, History, createHashHistory } from 'history';
 
 import { AppRouter, AppNotFound } from '../ui';
 import { EitherApp, MockedMounterMap, MockedMounterTuple } from '../test_types';
@@ -27,7 +27,15 @@ import { createRenderer, createAppMounter, createLegacyAppMounter } from './util
 describe('AppContainer', () => {
   let mounters: MockedMounterMap<EitherApp>;
   let history: History;
-  let navigate: ReturnType<typeof createRenderer>;
+  let update: ReturnType<typeof createRenderer>;
+
+  const navigate = (path: string) => {
+    history.push(path);
+    return update();
+  };
+
+  const mockMountersToMounters = () =>
+    new Map([...mounters].map(([appId, { mounter }]) => [appId, mounter]));
 
   beforeEach(() => {
     mounters = new Map([
@@ -38,14 +46,14 @@ describe('AppContainer', () => {
       createAppMounter('app3', '<div>App 3</div>', '/custom/path'),
     ] as Array<MockedMounterTuple<EitherApp>>);
     history = createMemoryHistory();
-    navigate = createRenderer(<AppRouter history={history} mounters={mounters} />, history.push);
+    update = createRenderer(<AppRouter history={history} mounters={mockMountersToMounters()} />);
   });
 
   it('calls mount handler and returned unmount function when navigating between apps', async () => {
     const dom1 = await navigate('/app/app1');
     const app1 = mounters.get('app1')!;
 
-    expect(app1.mount).toHaveBeenCalled();
+    expect(app1.mounter.mount).toHaveBeenCalled();
     expect(dom1?.html()).toMatchInlineSnapshot(`
       "<div><div>
       basename: /app/app1
@@ -53,11 +61,11 @@ describe('AppContainer', () => {
       </div></div>"
     `);
 
-    const app1Unmount = await app1.mount.mock.results[0].value;
+    const app1Unmount = await app1.mounter.mount.mock.results[0].value;
     const dom2 = await navigate('/app/app2');
 
     expect(app1Unmount).toHaveBeenCalled();
-    expect(mounters.get('app2')!.mount).toHaveBeenCalled();
+    expect(mounters.get('app2')!.mounter.mount).toHaveBeenCalled();
     expect(dom2?.html()).toMatchInlineSnapshot(`
       "<div><div>
       basename: /app/app2
@@ -70,29 +78,77 @@ describe('AppContainer', () => {
     mounters.set(...createAppMounter('spaces', '<div>Custom Space</div>', '/spaces/fake-login'));
     mounters.set(...createAppMounter('login', '<div>Login Page</div>', '/fake-login'));
     history = createMemoryHistory();
-    navigate = createRenderer(<AppRouter history={history} mounters={mounters} />, history.push);
+    update = createRenderer(<AppRouter history={history} mounters={mockMountersToMounters()} />);
 
     await navigate('/fake-login');
 
-    expect(mounters.get('spaces')!.mount).not.toHaveBeenCalled();
-    expect(mounters.get('login')!.mount).toHaveBeenCalled();
+    expect(mounters.get('spaces')!.mounter.mount).not.toHaveBeenCalled();
+    expect(mounters.get('login')!.mounter.mount).toHaveBeenCalled();
   });
 
   it('should not mount when partial route path has higher specificity', async () => {
     mounters.set(...createAppMounter('login', '<div>Login Page</div>', '/fake-login'));
     mounters.set(...createAppMounter('spaces', '<div>Custom Space</div>', '/spaces/fake-login'));
     history = createMemoryHistory();
-    navigate = createRenderer(<AppRouter history={history} mounters={mounters} />, history.push);
+    update = createRenderer(<AppRouter history={history} mounters={mockMountersToMounters()} />);
 
     await navigate('/spaces/fake-login');
 
-    expect(mounters.get('spaces')!.mount).toHaveBeenCalled();
-    expect(mounters.get('login')!.mount).not.toHaveBeenCalled();
+    expect(mounters.get('spaces')!.mounter.mount).toHaveBeenCalled();
+    expect(mounters.get('login')!.mounter.mount).not.toHaveBeenCalled();
+  });
+
+  it('should not remount when changing pages within app', async () => {
+    const { mounter, unmount } = mounters.get('app1')!;
+    await navigate('/app/app1/page1');
+    expect(mounter.mount).toHaveBeenCalledTimes(1);
+
+    // Navigating to page within app does not trigger re-render
+    await navigate('/app/app1/page2');
+    expect(mounter.mount).toHaveBeenCalledTimes(1);
+    expect(unmount).not.toHaveBeenCalled();
+  });
+
+  it('should not remount when going back within app', async () => {
+    const { mounter, unmount } = mounters.get('app1')!;
+    await navigate('/app/app1/page1');
+    expect(mounter.mount).toHaveBeenCalledTimes(1);
+
+    // Hitting back button within app does not trigger re-render
+    await navigate('/app/app1/page2');
+    history.goBack();
+    await update();
+    expect(mounter.mount).toHaveBeenCalledTimes(1);
+    expect(unmount).not.toHaveBeenCalled();
+  });
+
+  it('should not remount when when changing pages within app using hash history', async () => {
+    history = createHashHistory();
+    update = createRenderer(<AppRouter history={history} mounters={mockMountersToMounters()} />);
+
+    const { mounter, unmount } = mounters.get('app1')!;
+    await navigate('/app/app1/page1');
+    expect(mounter.mount).toHaveBeenCalledTimes(1);
+
+    // Changing hash history does not trigger re-render
+    await navigate('/app/app1/page2');
+    expect(mounter.mount).toHaveBeenCalledTimes(1);
+    expect(unmount).not.toHaveBeenCalled();
+  });
+
+  it('should unmount when changing between apps', async () => {
+    const { mounter, unmount } = mounters.get('app1')!;
+    await navigate('/app/app1/page1');
+    expect(mounter.mount).toHaveBeenCalledTimes(1);
+
+    // Navigating to other app triggers unmount
+    await navigate('/app/app2/page1');
+    expect(unmount).toHaveBeenCalledTimes(1);
   });
 
   it('calls legacy mount handler', async () => {
     await navigate('/app/legacyApp1');
-    expect(mounters.get('legacyApp1')!.mount.mock.calls[0]).toMatchInlineSnapshot(`
+    expect(mounters.get('legacyApp1')!.mounter.mount.mock.calls[0]).toMatchInlineSnapshot(`
       Array [
         Object {
           "appBasePath": "/app/legacyApp1",
@@ -104,7 +160,7 @@ describe('AppContainer', () => {
 
   it('handles legacy apps with subapps', async () => {
     await navigate('/app/baseApp');
-    expect(mounters.get('baseApp:legacyApp2')!.mount.mock.calls[0]).toMatchInlineSnapshot(`
+    expect(mounters.get('baseApp:legacyApp2')!.mounter.mount.mock.calls[0]).toMatchInlineSnapshot(`
       Array [
         Object {
           "appBasePath": "/app/baseApp",
diff --git a/src/core/public/application/integration_tests/utils.tsx b/src/core/public/application/integration_tests/utils.tsx
index b8ade4d1d8787..6367d1fa12697 100644
--- a/src/core/public/application/integration_tests/utils.tsx
+++ b/src/core/public/application/integration_tests/utils.tsx
@@ -26,19 +26,13 @@ import { App, LegacyApp, AppMountParameters } from '../types';
 import { MockedMounter, MockedMounterTuple } from '../test_types';
 
 type Dom = ReturnType<typeof mount> | null;
-type Renderer = (item: string) => Dom | Promise<Dom>;
+type Renderer = () => Dom | Promise<Dom>;
 
-export const createRenderer = (
-  element: ReactElement | null,
-  callback?: (item: string) => void | Promise<void>
-): Renderer => {
+export const createRenderer = (element: ReactElement | null): Renderer => {
   const dom: Dom = element && mount(<I18nProvider>{element}</I18nProvider>);
 
-  return item =>
+  return () =>
     new Promise(async resolve => {
-      if (callback) {
-        await callback(item);
-      }
       if (dom) {
         dom.update();
       }
@@ -50,19 +44,26 @@ export const createAppMounter = (
   appId: string,
   html: string,
   appRoute = `/app/${appId}`
-): MockedMounterTuple<App> => [
-  appId,
-  {
-    appRoute,
-    appBasePath: appRoute,
-    mount: jest.fn(async ({ appBasePath: basename, element }: AppMountParameters) => {
-      Object.assign(element, {
-        innerHTML: `<div>\nbasename: ${basename}\nhtml: ${html}\n</div>`,
-      });
-      return jest.fn(() => Object.assign(element, { innerHTML: '' }));
-    }),
-  },
-];
+): MockedMounterTuple<App> => {
+  const unmount = jest.fn();
+  return [
+    appId,
+    {
+      mounter: {
+        appRoute,
+        appBasePath: appRoute,
+        mount: jest.fn(async ({ appBasePath: basename, element }: AppMountParameters) => {
+          Object.assign(element, {
+            innerHTML: `<div>\nbasename: ${basename}\nhtml: ${html}\n</div>`,
+          });
+          unmount.mockImplementation(() => Object.assign(element, { innerHTML: '' }));
+          return unmount;
+        }),
+      },
+      unmount,
+    },
+  ];
+};
 
 export const createLegacyAppMounter = (
   appId: string,
@@ -70,9 +71,12 @@ export const createLegacyAppMounter = (
 ): MockedMounterTuple<LegacyApp> => [
   appId,
   {
-    appRoute: `/app/${appId.split(':')[0]}`,
-    appBasePath: `/app/${appId.split(':')[0]}`,
-    unmountBeforeMounting: true,
-    mount: legacyMount,
+    mounter: {
+      appRoute: `/app/${appId.split(':')[0]}`,
+      appBasePath: `/app/${appId.split(':')[0]}`,
+      unmountBeforeMounting: true,
+      mount: legacyMount,
+    },
+    unmount: jest.fn(),
   },
 ];
diff --git a/src/core/public/application/test_types.ts b/src/core/public/application/test_types.ts
index f5fb639eaa32c..3d992cb950eb4 100644
--- a/src/core/public/application/test_types.ts
+++ b/src/core/public/application/test_types.ts
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-import { App, LegacyApp, Mounter } from './types';
+import { App, LegacyApp, Mounter, AppUnmount } from './types';
 import { ApplicationService } from './application_service';
 
 /** @internal */
@@ -25,11 +25,19 @@ export type ApplicationServiceContract = PublicMethodsOf<ApplicationService>;
 /** @internal */
 export type EitherApp = App | LegacyApp;
 /** @internal */
+export type MockedUnmount = jest.Mocked<AppUnmount>;
+/** @internal */
 export type MockedMounter<T extends EitherApp> = jest.Mocked<Mounter<jest.Mocked<T>>>;
 /** @internal */
-export type MockedMounterTuple<T extends EitherApp> = [string, MockedMounter<T>];
+export type MockedMounterTuple<T extends EitherApp> = [
+  string,
+  { mounter: MockedMounter<T>; unmount: MockedUnmount }
+];
 /** @internal */
-export type MockedMounterMap<T extends EitherApp> = Map<string, MockedMounter<T>>;
+export type MockedMounterMap<T extends EitherApp> = Map<
+  string,
+  { mounter: MockedMounter<T>; unmount: MockedUnmount }
+>;
 /** @internal */
 export type MockLifecycle<
   T extends keyof ApplicationService,
diff --git a/src/core/public/application/ui/app_container.tsx b/src/core/public/application/ui/app_container.tsx
index 96ee91c7c21fb..153582e805fa1 100644
--- a/src/core/public/application/ui/app_container.tsx
+++ b/src/core/public/application/ui/app_container.tsx
@@ -65,7 +65,7 @@ export const AppContainer: FunctionComponent<Props> = ({ mounter, appId }: Props
 
     mount();
     return unmount;
-  });
+  }, [mounter]);
 
   return (
     <Fragment>
diff --git a/src/core/server/rendering/views/styles.tsx b/src/core/server/rendering/views/styles.tsx
index 40261321dcffc..f41627bcfe07f 100644
--- a/src/core/server/rendering/views/styles.tsx
+++ b/src/core/server/rendering/views/styles.tsx
@@ -78,7 +78,7 @@ export const Styles: FunctionComponent<Props> = ({ darkMode }) => {
             background-repeat: no-repeat;
             background-size: contain;
             /* SVG optimized according to http://codepen.io/tigt/post/optimizing-svgs-in-data-uris */
-            background-image: url'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMCIgaGVpZ2h0PSIzOSIgdmlld0JveD0iMCAwIDMwIDM5Ij4gIDxnIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+ICAgIDxwb2x5Z29uIGZpbGw9IiNGMDRFOTgiIHBvaW50cz0iMCAwIDAgMzQuNTQ3IDI5LjkyMiAuMDIiLz4gICAgPHBhdGggZmlsbD0iIzM0Mzc0MSIgZD0iTTAsMTQuNCBMMCwzNC41NDY4IEwxNC4yODcyLDE4LjA2MTIgQzEwLjA0MTYsMTUuNzM4IDUuMTgwNCwxNC40IDAsMTQuNCIvPiAgICA8cGF0aCBmaWxsPSIjMDBCRkIzIiBkPSJNMTcuMzc0MiwxOS45OTY4IEwyLjcyMSwzNi45MDQ4IEwxLjQzMzQsMzguMzg5MiBMMjkuMjYzOCwzOC4zODkyIEMyNy43NjE0LDMwLjgzODggMjMuNDA0MiwyNC4zMjY0IDE3LjM3NDIsMTkuOTk2OCIvPiAgPC9nPjwvc3ZnPg==');
+            background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMCIgaGVpZ2h0PSIzOSIgdmlld0JveD0iMCAwIDMwIDM5Ij4gIDxnIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+ICAgIDxwb2x5Z29uIGZpbGw9IiNGMDRFOTgiIHBvaW50cz0iMCAwIDAgMzQuNTQ3IDI5LjkyMiAuMDIiLz4gICAgPHBhdGggZmlsbD0iIzM0Mzc0MSIgZD0iTTAsMTQuNCBMMCwzNC41NDY4IEwxNC4yODcyLDE4LjA2MTIgQzEwLjA0MTYsMTUuNzM4IDUuMTgwNCwxNC40IDAsMTQuNCIvPiAgICA8cGF0aCBmaWxsPSIjMDBCRkIzIiBkPSJNMTcuMzc0MiwxOS45OTY4IEwyLjcyMSwzNi45MDQ4IEwxLjQzMzQsMzguMzg5MiBMMjkuMjYzOCwzOC4zODkyIEMyNy43NjE0LDMwLjgzODggMjMuNDA0MiwyNC4zMjY0IDE3LjM3NDIsMTkuOTk2OCIvPiAgPC9nPjwvc3ZnPg==');
           }
 
           .kibanaWelcomeTitle {