openedx / openedx/frontend-app-learning
Migrate course custom (static) pages into the learning MFE
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 70
- Forks
- 335
- Avg merge
- 1d 17h
- Merged PRs (30d)
- 35
Description
Context
Course teams can add custom static HTML pages to a course - things like a syllabus or a resources page - that show up as tabs in the course navigation. These are backed by the StaticTabBlock XBlock (xmodule/html_block.py, tab type static_tab). Authoring already moved to the authoring MFE; the learner-facing render is the piece that's still legacy.
Today the learning MFE shows the tab in its nav but links out to the old server-rendered page. The tab list comes from CourseTabSerializer (lms/djangoapps/course_home_api/course_metadata/serializers.py), whose url for a static tab resolves to the legacy StaticCourseTabView at /courses/{course_id}/{tab_slug}/. So clicking a custom page leaves the MFE and loads courseware/static_tab.html. This issue is about rendering those pages inside the learning MFE so the legacy view can go away.
What exists today
- Legacy render:
StaticCourseTabView(lms/djangoapps/courseware/views/views.py:475) at/courses/{course_id}/{tab_slug}/. It resolves the tab to a block and renders the block'sSTUDENT_VIEWfragment viaget_static_tab_fragment(views.py:1298). - The block is addressable:
get_static_tab_fragmentbuilds the usage key ascourse.id.make_usage_key('static_tab', tab.url_slug), i.e.block-v1:{course}+type@static_tab+block@{url_slug}. That means it can be rendered chromelessly through the existingrender_xblockendpoint at/xblock/{usage_key}. - MFE nav: the learning MFE already renders the tab list in
src/course-tabs/(CourseTabLinksList.tsx), one{ url, title, slug }link per tab. For a static tab theurlis the legacy page. - Iframe precedent: the MFE already embeds XBlock content through
render_xblockfor units -src/courseware/course/sequence/Unit/ContentIFrame.jsxandgetIFrameUrlinUnit/urls.ts, which builds${LMS_BASE_URL}/xblock/{id}. The same mechanism covers a static tab.
Proposed approach
Render the custom page as an in-MFE tab that iframes the static tab block, reusing the unit iframe machinery:
- Backend (openedx-platform): give the MFE what it needs to render in place instead of linking out. Extend the tab data (
CourseTabSerializeror a companion field) so a static tab carries its block usage key, and point the tab'surlat an in-MFE route rather than/courses/{id}/{slug}/. - MFE route + page: add a route for custom pages and a component that iframes
/xblock/{static_tab_usage_key}via the existingContentIFramepath. Keep the tab highlighted in the nav while it's open. - Retire the legacy view: once the MFE serves these, the
StaticCourseTabViewrender, itsstatic_tab.htmltemplate, and the legacy taburlcan be removed. That removal is tracked back in openedx/openedx-platform#38936.
Open questions
- Access control:
StaticCourseTabViewrunsget_course_with_accessand shows limited-access warnings. Confirmrender_xblockenforces the same access for astatic_tabblock, or add it. - Multiple pages: a course can have several static tabs. The route should key off the tab slug and resolve the right block.
- Styling / chrome: the legacy page renders inside the LMS course chrome; in the MFE it should sit inside the course tab layout with the block iframed as content. Confirm the block's own CSS/JS load correctly through
render_xblock. - Height / resize: reuse
useIFrameBehaviorso the iframe resizes to content, same as units. - Transition: decide whether to ship the MFE route behind the existing tab data first and drop the legacy view in a follow-up, or do both together.
References
- Tracker: openedx/openedx-platform#38936 (Course Home: Custom Pages row).
- Legacy view:
lms/djangoapps/courseware/views/views.py:475(StaticCourseTabView),:1298(get_static_tab_fragment); templatelms/templates/courseware/static_tab.html; routelms/urls.py:785-790. - Block:
xmodule/html_block.py(StaticTabBlock),xmodule/tabs.py:297(StaticTab). - Tab serialization:
lms/djangoapps/course_home_api/course_metadata/serializers.py(CourseTabSerializer). - MFE iframe pattern:
src/courseware/course/sequence/Unit/ContentIFrame.jsx,Unit/urls.ts(getIFrameUrl),Unit/hooks/useIFrameBehavior.ts; nav insrc/course-tabs/.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with CourseTabSerializer and src/course-tabs/, then compare the existing iframe flow in ContentIFrame.jsx, Unit/urls.ts, and useIFrameBehavior.ts. Trace StaticCourseTabView and get_static_tab_fragment in the platform files to verify access behavior and usage-key handling. Done means static tabs open inside the learning MFE with correct navigation state, access behavior, and iframe sizing, without relying on the legacy page.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, react
- Domain
- api, backend, frontend
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100