# Using a CDN to speed up MFEs

**URL:** <https://discuss.openedx.org/t/using-a-cdn-to-speed-up-mfes/10967>\
**Category:** Site Operators\
**Tags:** tutor\
**Created:** [August 15, 2023, 12:19pm UTC](https://discuss.openedx.org/t/using-a-cdn-to-speed-up-mfes/10967 "2023-08-15T12:19:45Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![MoisesGonzalezS](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.openedx.org/moisesgonzalezs/32/1996_2.png) [@MoisesGonzalezS](https://discuss.openedx.org/u/MoisesGonzalezS)\
**Post date:** [August 15, 2023, 12:19pm UTC](https://discuss.openedx.org/t/using-a-cdn-to-speed-up-mfes/10967/1 "2023-08-15T12:19:45Z")

</div>

I’ve been playing around with the idea of using a CDN service to speed up the MFE pages, in the end it’s just a javascript bundle so it seems like a good target for this.

I’ve done this with edxapp simply by using cloudfront and setting the LMS as the origin server. I’ve found this approach to be pretty straightforward, I just need to configure my cloudfront distribution and add `STATIC_URL = "https://d1234567890.cloudfront.net/static/"` to my settings.

I tried to do something similar with the MFEs, but ran into some blockers. The approach that seemed more similar to the previous one was to use [`output.publicPath`](https://webpack.js.org/configuration/output/#outputpublicpath) and set it to the CDN URL. The problem is that it doesn’t seem to be particularly easy to modify `publicPath` in the [tutor-mfe](https://github.com/overhangio/tutor-mfe/blob/master/tutormfe/templates/mfe/build/mfe/Dockerfile#L75) plugin at the moment. Even then, after applying manual changes and setting `ENV PUBLIC_PATH='https://d1234567890.cloudfront.net/{{ app_name }}/'`, I was able to load the assets from cloudfront but I encountered a blank page when accessing the app. Upon further investigation, the error seems to be related to the [history object](https://github.com/openedx/frontend-platform/blob/master/src/initialize.js#L102) which expects and actual path instead of the full URL.

In the end, [modifying frontend-build to use an additional variable](https://github.com/eduNEXT/frontend-build/blob/273ba0c27c78cb3b9d04125c5d1b8100a0e7b3f7/config/webpack.prod.config.js#L57) when configuring `output.publicPath` did the trick.

One of my teammates faced the same issue and actually opened a PR ([frontend-build#398](https://github.com/openedx/frontend-build/pull/398)).

I was wondering what kind of deployment strategies do you use for higher traffic installations, in particular those of you using tutor. I think this approach is quite straightforward once the frontend-build changes land, you would need a new MFE image but that doesn’t seem like a big deal.

I would like to push that PR forward, maybe have a discussion at the next BTR meeting?

---

<div class="post-metadata">

**Author:** ![regis](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.openedx.org/regis/32/2640_2.png) [@regis](https://discuss.openedx.org/u/regis)\
**Post date:** [August 15, 2023, 2:05pm UTC](https://discuss.openedx.org/t/using-a-cdn-to-speed-up-mfes/10967/2 "2023-08-15T14:05:40Z")

</div>

This looks like a great initiative, and somewhat similar to some of the work by @ghassan. Do you have some numbers you can share in terms of performance improvement?

---

<div class="post-metadata">

**Author:** ![ghassan](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.openedx.org/ghassan/32/2779_2.png) [@ghassan](https://discuss.openedx.org/u/ghassan)\
**Post date:** [August 16, 2023, 7:37am UTC](https://discuss.openedx.org/t/using-a-cdn-to-speed-up-mfes/10967/3 "2023-08-16T07:37:45Z")

</div>

Yes this relate to an initiative I suggested in the DevOps meeting back in the conference, it’s detialed here [Serving MFEs through Cloudflare · Issue #14 · openedx/wg-devops · GitHub](https://github.com/openedx/wg-devops/issues/14) .

I suggested using `Cloudflare` but it’s really doesn’t matter, be it, AWS Cloudfront, Fastly…etc assuming the end goal is to server the MFEs assests from a CDN.

It’s worth noting that by average a MFE assests is ~5MB, and compressed it’s ~1MB, so the effect of this on perforamance would depend on the connection speed/latency of the user on the platform and the load on the server _it’s just an educated guess I haven’t tested it yet_.

I think though there is a slight difference between what you are suggesting and what I had in mind, I wanted not only to enable the MFEs assests to be served through CDNs but also to simplify the workflow of an MFEs to get deployed, that is each MFE would be deployed/built by an external service and the URL of the the MFE would be unique. e.g. instead `app.myopenedx.com/learning` it would be `learning.myopenedx.com` and with this approach you no longer need to modifiy the `publicPath`. The only thing is that it require more configuration when setting up the platform.

Regarding how the assets get uploaded to `Cloudfront` did you use a webpack plugin to do that? may this one [GitHub - matrus2/webpack-s3-uploader: Upload all your assets to AWS S3 during webpack build.](https://github.com/matrus2/webpack-s3-uploader)

---

<div class="post-metadata">

**Author:** ![MoisesGonzalezS](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.openedx.org/moisesgonzalezs/32/1996_2.png) [@MoisesGonzalezS](https://discuss.openedx.org/u/MoisesGonzalezS)\
**Post date:** [August 16, 2023, 5:12pm UTC](https://discuss.openedx.org/t/using-a-cdn-to-speed-up-mfes/10967/4 "2023-08-16T17:12:13Z")

</div>

> Do you have some numbers you can share in terms of performance improvement?

I only ran a small test in a k8s cluster that retrieved the static files used by the learning MFE (the ones listed when you check your network tab while accessing an wrong route `/learning/nogood`) with X amount of simultaneous connections. Looping through these files for 1 minute with 20 connections I made 3.1k requests and by grouping the files I got a response time (p95) of 2.35s. Doing the same test with 80 connections I made 4k requests and the p95 increased up to 9s. Repeating the 80 connections using cloudfront is obviously way faster with 20k+ requests and p(95) of 700ms.

This amount of load is obviously excessive in most scenarios, but it does show degradation in the performance and using a CDN seems like an easy fix. I’m not _that_ concerned about speeding up delivery for users in ideal conditions where their own connection is the bottleneck, but rather when my service is under heavy load (an exam for example).

> Regarding how the assets get uploaded to `Cloudfront` did you use a webpack plugin to do that? may this one [GitHub - matrus2/webpack-s3-uploader: Upload all your assets to AWS S3 during webpack build.](https://github.com/matrus2/webpack-s3-uploader)

What I did was set the MFE domain (`apps.myopenedx.com`) as the origin for my distribution, at first cloudfront would hit my server for the files but after that it will cache them and serve them to the users.

> I think though there is a slight difference between what you are suggesting and what I had in mind, I wanted not only to enable the MFEs assests to be served through CDNs but also to simplify the workflow of an MFEs to get deployed, that is each MFE would be deployed/built by an external service and the URL of the the MFE would be unique.

I would like to eventually land on a more sophisticated method that allows me to have independent deployments for each MFE, especially when we are working on a single fork but have to rebuild all of them. But I think this approach offers minimal friction at the moment.

I also have a use case for hosting the MFEs on certain paths: we want to use multiple sites, and serving the MFE in `lms.myopenedx.com/learning` is really handy.

---

<div class="post-metadata">

**Author:** ![Zachary\_Trabookis](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.openedx.org/zachary_trabookis/32/753_2.png) [@Zachary\_Trabookis](https://discuss.openedx.org/u/Zachary_Trabookis)\
**Post date:** [July 23, 2024, 9:48pm UTC](https://discuss.openedx.org/t/using-a-cdn-to-speed-up-mfes/10967/5 "2024-07-23T21:48:32Z")

</div>

@MoisesGonzalezS  
I applied the fix [Jlc/parser to know path by johanseto · Pull Request #568 · openedx/frontend-platform (github.com)](https://github.com/openedx/frontend-platform/pull/568) and I’m setting this environment variable like so when building out the MFE frontend-app-grading.

```auto
ENV PUBLIC_PATH='https://XXXXXXXXXXXX.cloudfront.net/gradebook/'
# ENV PUBLIC_PATH='/gradebook/'

```

For some reason the header and footer load fine but the content and XHR calls to get information from the LMS APIs don’t seem to be called. I don’t see any errors in the logs files either or in the browser console.

Could you help me figure out why the gradebook MFE content won’t load. Learning, account, profile frontends load fine without error.

All the JavaScript files seem to load fine with HTTP 200 response I see information in the files.

 ![image](https://us1.discourse-cdn.com/flex020/uploads/openedx/original/2X/d/d7ed0a988204e13c77344a214be57e5b5741bffd.png)

It looks like the `frontend-component-header` and `frontend-component-footer` load successfully.

 ![image](https://us1.discourse-cdn.com/flex020/uploads/openedx/original/2X/5/5830990c301001770009cd0746771e106563e33d.png)

I just have `publicPath: process.env.PUBLIC_PATH || '/',` in the `/frontend-build/config/webpack.prod.config.js` for my release and I’m guessing that this `PUBLIC_PATH` is the full path the Cloudfront URL.  
 ![image](https://us1.discourse-cdn.com/flex020/uploads/openedx/original/2X/0/07454da25d81a2f1f384710a6c5d371d3e0a89eb.png)

The `frontend-app-learning` loads no problem with the following configuration:

```auto
ENV PUBLIC_PATH='https://XXXXXXXXXXXX.cloudfront.net/learning/'
# ENV PUBLIC_PATH='/learning/'

```

 ![image](https://us1.discourse-cdn.com/flex020/uploads/openedx/original/2X/3/31610413d07072d1e99a8bd9925e7845c10f71b2.png)

After building out the MFE with `tutor images build mfe` it copies the `/dist` files for each MFE up to an S3 bucket.

 ![image](https://us1.discourse-cdn.com/flex020/uploads/openedx/original/2X/a/aaf8bdc24065d61d5c68ac28c31785da11ce0b15.png)

I created a Lambda function that would be called on the origin request of the Cloudfront behavior and check to see if the request was a path and route to the corresponding S3 bucket directory for the given MFE app. The example below looks for `/gradebook/` path and routes the request to the S3 `./gradebook/index.html` page.

 ![image](https://us1.discourse-cdn.com/flex020/uploads/openedx/original/2X/8/8a33fcc535e17a31f4b6b5c260727c5ab31cebb9.png)

---

<div class="post-metadata">

**Author:** ![MoisesGonzalezS](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.openedx.org/moisesgonzalezs/32/1996_2.png) [@MoisesGonzalezS](https://discuss.openedx.org/u/MoisesGonzalezS)\
**Post date:** [July 23, 2024, 10:00pm UTC](https://discuss.openedx.org/t/using-a-cdn-to-speed-up-mfes/10967/6 "2024-07-23T22:00:52Z")

</div>

IIRC, the gradebook routing was kind of wonky, you may need to backport this commit: [fix: Route with PUBLIC\_PATH · openedx/frontend-app-gradebook@cb65877 · GitHub](https://github.com/openedx/frontend-app-gradebook/commit/cb658776c6420f6291c5247a1ad604e1e38957dd)

Keep in mind that change was done after the react router upgrade. I don’t know how well it plays with the previous version.

---

<div class="post-metadata">

**Author:** ![Zachary\_Trabookis](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.openedx.org/zachary_trabookis/32/753_2.png) [@Zachary\_Trabookis](https://discuss.openedx.org/u/Zachary_Trabookis)\
**Post date:** [July 25, 2024, 3:33pm UTC](https://discuss.openedx.org/t/using-a-cdn-to-speed-up-mfes/10967/7 "2024-07-25T15:33:57Z")

</div>

@MoisesGonzalezS  
Thanks for this solution. This seemed to help get the gradebook to load.

Even after the path update above, the Gradebook MFE wouldn’t load until I removed the `exact` parameter then everything worked as expected.

```auto
            <Route
              exact
              path="/:courseId"
              component={GradebookPage}
            />

```

We’re still on the following, however, v6 of `react-router-dom` mentions the following. Anyway, I noticed that `exact` is gone anyhow in the redwood release [frontend-app-gradebook/src/App.jsx at open-release/redwood.master · openedx/frontend-app-gradebook (github.com)](https://github.com/openedx/frontend-app-gradebook/blob/open-release/redwood.master/src/App.jsx#L21-L24) but that’s probably because this release is using version 6.

> [Upgrading from v5 v6.25.1 | React Router](https://reactrouter.com/en/main/upgrading/v5#relative-routes-and-links)

- `<Route exact>` is gone. Instead, routes with descendant routes (defined in other components) use a trailing `*` in their path to indicate they match deeply

```auto
    "react-router": "5.2.0",
    "react-router-dom": "5.2.0",

```

---

<div class="post-metadata">

**Author:** ![Zachary\_Trabookis](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.openedx.org/zachary_trabookis/32/753_2.png) [@Zachary\_Trabookis](https://discuss.openedx.org/u/Zachary_Trabookis)\
**Post date:** [September 26, 2024, 6:11pm UTC](https://discuss.openedx.org/t/using-a-cdn-to-speed-up-mfes/10967/8 "2024-09-26T18:11:21Z")

</div>

@MoisesGonzalezS  
Did you run into issues with the gradebook MFE looking for the first parameter for `courseId`. We used a Cloudfront distribution that pointed to S3 origin with `/gradebook` files in their own directory. When calling that endpoint with `https://apps.maple3.ew-dev.com/gradebook/course-v1:CA+FAA-ACS-AM-IA-ACE+DEVELOPMENT`, the MFE app picked this courseId up as `'gradebook'` value and broke part of the logic in the app.

The MFE container in a default `tutor local` install builds all the MFE apps and puts the output at `/openedx/dist` with a corresponding directory for each app. I also noticed that calls has this defined for the gradebook. Notice the `uri strip_prefix /gradebook` portion. Is this how it prevents the MFE gradebook app from keep that value in the URL?

```auto
# Caddy configuration at /etc/caddy/Caddyfile for the Gradebook MFE.
    @mfe_gradebook {
        path /gradebook /gradebook/*
    }
    handle @mfe_gradebook {
        uri strip_prefix /gradebook
        root * /openedx/dist/gradebook
        try_files /{path} /index.html
        file_server
    }

```

I think the only other alternative is to keep each MFE in it’s own separate Cloudfront/MFE configuration. I tried putting all the built MFE files into a single Cloudfront/S3 configuration, but I seem to run into the gradebook MFE breaking because of how the URL is structured.

Let me know how you handled this.

---

<div class="post-metadata">

**Author:** ![Zachary\_Trabookis](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.openedx.org/zachary_trabookis/32/753_2.png) [@Zachary\_Trabookis](https://discuss.openedx.org/u/Zachary_Trabookis)\
**Post date:** [September 26, 2024, 8:30pm UTC](https://discuss.openedx.org/t/using-a-cdn-to-speed-up-mfes/10967/9 "2024-09-26T20:30:27Z")

</div>

@MoisesGonzalezS  
I was able to get Cloudfront/S3 for the MFE gradebook endpoint working like so. It may still be helpful to see how you’ve done it on your end just to confirm though.

A separate Cloudfront distribution was set up to handle the `gradebook.chooseaerospace.maple3.ew-dev.com` domain with SSL certificate generated by AWS Certificate Manager. For now, I’m just testing the grading MFE endpoint for one of our branded sites.

 ![image](https://us1.discourse-cdn.com/flex020/uploads/openedx/original/2X/f/fe82b88b8099e99400681395e17ddb5b71423c54.png)

The `Origin path = '/gradebook'` tells the Cloudfront request to forward all traffic to that S3 bucket `/gradebook` directory path.

 ![image](https://us1.discourse-cdn.com/flex020/uploads/openedx/original/2X/1/1541ff7a00280ed87c9467cf20083db3cb437583.png)

The custom error page for error `403 Forbidden` response from Cloudfront will be generated after the user tries to access a gradebook MFE React route `path="/:courseId"` of value `/course-v1:CA+FAA-ACS-AM-IA-ACE+DEVELOPMENT`. The Cloudfront error page will redirect the user to the React entry page `/gradebook/index.html` on S3 origin and a HTTP 200 success response is returned.

 ![image](https://us1.discourse-cdn.com/flex020/uploads/openedx/original/2X/9/9ffe8778e5724927d122736acea70ceec288df9e.png)

Here is the page working without error concerning the `<h3>course-id</h3>` and link `<< Back to Dashboard` to take the learner back to the LMS MFE for the course.

 ![image](https://us1.discourse-cdn.com/flex020/uploads/openedx/original/2X/1/1c2ddb715e9fcb2a7941307792ff60be9cee7bc5.png)
