diff --git a/src/generic/path-fixes/PathFixesProvider.jsx b/src/generic/path-fixes/PathFixesProvider.jsx
new file mode 100644
index 00000000..32156609
--- /dev/null
+++ b/src/generic/path-fixes/PathFixesProvider.jsx
@@ -0,0 +1,42 @@
+import { Redirect, useLocation } from 'react-router-dom';
+import PropTypes from 'prop-types';
+
+import { sendTrackEvent } from '@edx/frontend-platform/analytics';
+
+/**
+ * We have seen evidence of learners hitting MFE pages with spaces instead of plus signs (which are used commonly
+ * in our course keys). It's possible something out there is un-escaping our paths before sending learners to them.
+ *
+ * So this provider fixes those paths up and logs it so that we can try to fix the source.
+ *
+ * This might be temporary, based on how much we can fix the sources of these urls-with-spaces.
+ */
+const PathFixesProvider = ({ children }) => {
+ const location = useLocation();
+
+ // We only check for spaces. That's not the only kind of character that is escaped in URLs, but it would always be
+ // present for our cases, and I believe it's the only one we use normally.
+ if (location.pathname.includes(' ')) {
+ const newLocation = {
+ ...location,
+ pathname: location.pathname.replaceAll(' ', '+'),
+ };
+
+ sendTrackEvent('edx.ui.lms.path_fixed', {
+ new_path: newLocation.pathname,
+ old_path: location.pathname,
+ referrer: document.referrer,
+ search: location.search,
+ });
+
+ return ();
+ }
+
+ return children; // pass through
+};
+
+PathFixesProvider.propTypes = {
+ children: PropTypes.node.isRequired,
+};
+
+export default PathFixesProvider;
diff --git a/src/generic/path-fixes/PathFixesProvider.test.jsx b/src/generic/path-fixes/PathFixesProvider.test.jsx
new file mode 100644
index 00000000..c20bbd99
--- /dev/null
+++ b/src/generic/path-fixes/PathFixesProvider.test.jsx
@@ -0,0 +1,65 @@
+import React from 'react';
+import { MemoryRouter, Route } from 'react-router-dom';
+
+import { sendTrackEvent } from '@edx/frontend-platform/analytics';
+
+import { initializeMockApp, render } from '../../setupTest';
+import PathFixesProvider from '.';
+
+initializeMockApp();
+jest.mock('@edx/frontend-platform/analytics');
+
+describe('PathFixesProvider', () => {
+ let testLocation;
+
+ beforeAll(() => {
+ Object.defineProperty(document, 'referrer', { value: 'https://example.com/foo' });
+ testLocation = null;
+ sendTrackEvent.mockClear();
+ });
+
+ function buildAndRender(path) {
+ render(
+
+
+ {
+ testLocation = routeProps.location;
+ return null;
+ }}
+ />
+
+ ,
+ );
+ }
+
+ it('does not redirect for normal path', () => {
+ buildAndRender('/course/course-v1:org+course+run/home');
+ expect(testLocation.pathname).toEqual('/course/course-v1:org+course+run/home');
+ expect(sendTrackEvent).toHaveBeenCalledTimes(0);
+ });
+
+ it('does redirect for path with spaces', () => {
+ buildAndRender('/course/course-v1:org course run/home');
+ expect(testLocation.pathname).toEqual('/course/course-v1:org+course+run/home');
+ expect(sendTrackEvent).toHaveBeenCalledWith('edx.ui.lms.path_fixed', {
+ new_path: '/course/course-v1:org+course+run/home',
+ old_path: '/course/course-v1:org course run/home',
+ referrer: 'https://example.com/foo',
+ search: '',
+ });
+ });
+
+ it('does not change search part of URL', () => {
+ buildAndRender('/course/course-v1:org course run/home page?donuts=yes please');
+ expect(testLocation.pathname).toEqual('/course/course-v1:org+course+run/home+page');
+ expect(testLocation.search).toEqual('?donuts=yes please');
+ expect(sendTrackEvent).toHaveBeenCalledWith('edx.ui.lms.path_fixed', {
+ new_path: '/course/course-v1:org+course+run/home+page',
+ old_path: '/course/course-v1:org course run/home page',
+ referrer: 'https://example.com/foo',
+ search: '?donuts=yes please',
+ });
+ });
+});
diff --git a/src/generic/path-fixes/index.js b/src/generic/path-fixes/index.js
new file mode 100644
index 00000000..e07e63a6
--- /dev/null
+++ b/src/generic/path-fixes/index.js
@@ -0,0 +1 @@
+export { default } from './PathFixesProvider';
diff --git a/src/index.jsx b/src/index.jsx
index fd6d8c35..30d48126 100755
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -29,56 +29,59 @@ import { fetchDatesTab, fetchOutlineTab, fetchProgressTab } from './course-home/
import { fetchCourse } from './courseware/data';
import initializeStore from './store';
import NoticesProvider from './generic/notices';
+import PathFixesProvider from './generic/path-fixes';
subscribe(APP_READY, () => {
ReactDOM.render(
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- (
- fetchProgressTab(courseId, match.params.targetUserId)}
- slice="courseHome"
- >
-
+
+
+
+
+
+
+
+
+
- )}
- />
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+ (
+ fetchProgressTab(courseId, match.params.targetUserId)}
+ slice="courseHome"
+ >
+
+
+ )}
+ />
+
+
+
+
+
+
+
+
+
+
,
document.getElementById('root'),
);