
How to Host an HTML Course Without an LMS
Table of Contents
- Publish an HTML Course Without Running an LMS
- 1. Confirm That Static HTML Course Hosting Fits Your Course
- 2. Check the Course Export Before Publishing
- 3. Verify Relative Paths Before You Publish the HTML Course
- 4. Choose a Host and an Access Level
- 5. Upload the Course to Cloudflare Pages
- 6. Restrict the Course to Named Learners With Cloudflare Access
- 7. Test the Live Link as a Learner
- 8. Share, Measure, Collect Feedback and Update
- 9. Troubleshoot Common Static HTML Course Hosting Failures
- Final Thoughts
- Publish an HTML Course Without Running an LMS
- 1. Confirm That Static HTML Course Hosting Fits Your Course
- 2. Check the Course Export Before Publishing
- 3. Verify Relative Paths Before You Publish the HTML Course
- 4. Choose a Host and an Access Level
- 5. Upload the Course to Cloudflare Pages
- 6. Restrict the Course to Named Learners With Cloudflare Access
- 7. Test the Live Link as a Learner
- 8. Share, Measure, Collect Feedback and Update
- 9. Troubleshoot Common Static HTML Course Hosting Failures
- Final Thoughts
Publish an HTML Course Without Running an LMS
An exported course can look finished on your computer and still be hard to share. Email rejects the folder. A cloud drive shows a list of files instead of a course. When index.html is online, it behaves differently.
With e-learning static hosting, you publish an export of HTML, CSS, JavaScript, images, audio and video as an ordinary website. A static host only serves files. Learners get one browser link. They install nothing, and they need no account in an LMS (learning management system).
In short, make sure that the course runs fully in the browser. Keep the exported folder intact. Upload it to a static host such as Cloudflare Pages. If the course is not public, limit it to named learners with Cloudflare Access. Test it as a learner. Update it behind the same URL. Static hosting serves files. It does not give you LMS enrollment, SCORM reporting or server-side learning records.
1. Confirm That Static HTML Course Hosting Fits Your Course
Before you upload, find out what the course needs from its host. A static course runs in the browser of the learner. The host delivers files. JavaScript handles slides, menus, simple quizzes and the progress that the browser saves.
This setup suits many client courses, product tutorials, onboarding guides, interactive presentations and self-paced resources. It suits them best when you need one stable link more than completion reports.
| Requirement | Static hosting | LMS or application server |
|---|---|---|
| Display HTML, images, audio and video | Yes | Yes |
| Run browser-side JavaScript (menus, quizzes, animations) | Yes | Yes |
| Restrict access to the published link | Yes, if the host has access control | Yes |
| Record SCORM completion and scores | No | Yes |
| Enroll learners in assigned courses | No | Yes |
| Issue certificates from verified records | No | Usually |
| Run databases or custom server code | No | Yes |
Inspect the export outside the authoring tool. If it contains standard website files and needs no connection to an LMS, static hosting probably suits it.
A SCORM package is different. SCORM is a standard that lets courses report to an LMS. The SCORM run-time overview explains that all communication between SCORM content and the LMS goes through a JavaScript API. The LMS provides this API. The content uses it to report scores and completion. A static host has no such API. The pages can open, but completion, resume state, pass/fail status and score reporting do not work.
2. Check the Course Export Before Publishing
A reliable export includes an entry page, usually index.html, and every file that the page references. Static hosts serve index.html at the root of the uploaded folder as the home page. If the entry page is one folder deeper, or has a different name, the root address does not show your course.
A typical folder looks like this:
client-safety-course/
index.html
css/
course.css
js/
course.js
images/
cover.webp
audio/
lesson-01.mp3
modules/
lesson-01.html
data/
course.json
Before you publish, inspect these items:
| Item | What to check | Why it matters |
|---|---|---|
| Entry page | index.html is at the root of the folder that you upload |
The course needs a predictable starting page |
| Folder structure | Asset and module folders stay in their original locations | Moving files breaks relative references |
| File names | Capitalization matches the references in the HTML | Web servers treat Logo.png and logo.png as different files |
| Media | Audio and video files are included, not linked to local paths | Missing media makes lessons unusable |
| File sizes | No single file is larger than the host allows | Cloudflare Pages, for example, rejects files over 25 MiB |
| Licenses | You have the right to share the fonts, stock media and course material with this audience | Publishing changes who can access the files |
| Sensitive files | Drafts, answer keys, source files and private notes are not in the folder | A visitor can request any uploaded file by its URL |
Do not flatten the export (move all files into one folder). A path such as images/cover.webp needs the images folder beside index.html. Unless you understand its code, keep the structure as exported.
3. Verify Relative Paths Before You Publish the HTML Course
Broken paths are the most common reason why a course works in the authoring preview but fails online. Inspect the references in index.html:
<link rel="stylesheet" href="css/course.css">
<script src="js/course.js"></script>
<img src="/assets/cover.webp" alt="Course cover">
<a href="modules/lesson-01.html">Start lesson</a>
These are relative paths (paths that start from the current file). The browser resolves them from the location of the current document. MDN’s guide to links says that a relative link can break if you move the document or its target. Paths that begin with / start at the website root. Paths that begin with file:///, a Windows drive letter or a local user folder point to the author’s computer. These paths fail for learners.
Test the export in this order:
- Open
index.html. Go through every lesson. - Make sure that images, fonts, audio, video, downloads and quizzes work.
- Open the browser’s developer tools. Look in the Console for JavaScript errors.
- Look in the Network panel for red requests or
404 Not Foundresponses. - If the course loads JSON, modules or other files with
fetch(), repeat the test through a small local web server.
Here is a typical failure. A five-module compliance course works on the author’s desktop, but its images disappear online. The cause is that the HTML references C:\Users\Name\Course\images. To correct it, export again with relative paths such as images/photo-01.jpg. You do not have to change the lessons.
4. Choose a Host and an Access Level
Before you choose a host, decide who can open the course. Hosts differ most in access control.
| Audience | Practical setup | Trade-off |
|---|---|---|
| Anyone with the link (open tutorials, resources linked from a website) | Cloudflare Pages, Netlify or GitHub Pages with no access control | Anyone can open and forward the course, and can possibly find it |
| Named learners or the staff of one company | Cloudflare Pages plus Cloudflare Access with an email one-time PIN | Before the course opens, learners type their email and a code from their inbox |
| A known group that shares one password | Netlify password protection (Pro plan or higher) or Vercel Password Protection (paid add-on on Pro, included on Enterprise) | One shared secret identifies the group, not the person |
For most people, GitHub Pages fits only the first row. Its sites are publicly available on the internet even when the repository is private. Pages sites with access control need GitHub Enterprise Cloud.
The rest of this guide uses Cloudflare Pages. It accepts a dragged folder, and it works with Cloudflare Access for access by email. The checks in sections 2, 3, 7 and 9 apply to any host.
5. Upload the Course to Cloudflare Pages
Use Cloudflare Pages direct upload:
- Sign in to the Cloudflare dashboard. Open Workers & Pages.
- Select Create application > Get started > Drag and drop your files.
- Enter a project name, such as
acme-manager-training. Cloudflare will serve the course atacme-manager-training.pages.dev. - Drag in the exported course folder (the one that contains
index.html), or a ZIP of it. Select Deploy site. - Open the address. Make sure that the first lesson appears.
Drag and drop accepts up to 1,000 files and 25 MiB per file. Some authoring tools export many small files. If your export is over the limit, use Wrangler. Wrangler is the command-line tool of Cloudflare, and it accepts up to 20,000 files. Sign in once:
npx wrangler login
If the project does not exist yet, create it:
npx wrangler pages project create acme-manager-training
Then deploy the course folder:
npx wrangler pages deploy ./client-safety-course --project-name=acme-manager-training
Upload only the export, not a parent folder that also has drafts and source files. Anyone who can reach the site can request every file that you upload.
6. Restrict the Course to Named Learners With Cloudflare Access
If the course is public, skip this section. For a client team or a paid workshop, Cloudflare Access puts a sign-in page in front of the whole site. This includes every image, video and PDF. Cloudflare shows the sign-in page before it serves any file.
First, turn on email codes. In the Cloudflare dashboard, go to Zero Trust > Integrations > Identity providers. Select Add new identity provider. Choose One-time PIN. Then a learner enters their email on the Access login page and selects Send login code. The learner types the code from their inbox. According to the one-time PIN documentation, each code works once. It expires 10 minutes after the learner requests it.
Then protect the course address:
- In Workers & Pages, open the project. Go to Settings > General and select Enable access policy. This setting alone protects only preview deployments, not
acme-manager-training.pages.dev. - Follow Cloudflare’s steps for the production pages.dev address. Open the Access application that the toggle created. Remove the wildcard from its subdomain, so that it covers the main address. Save the application. Then enable the Pages access policy again, so that previews stay protected.
- Edit the Allow policy of the application. Under Include, use the Emails selector for a list of named learners. For everyone at the domain of the client, use Emails ending in.
- If you add a custom domain, create a separate self-hosted Access application for it, as the same Cloudflare page describes.
Our Cloudflare Pages access guide shows these screens in more detail.
People who are not on the list never receive a code. But the login page shows the same “code has been emailed” message to everyone. If a learner says that the code never arrived, look at the policy first. If the learner sees “This One-Time PIN has already been used”, a mail security scanner probably opened the link in the email first. The learner can request a new code.
If you prefer one shared password, publish the same folder on Netlify. While you are signed in, drag the folder onto Netlify Drop. Then set a password under Project configuration > General > Visitor access. This feature needs a Pro plan or higher.
7. Test the Live Link as a Learner
Open the URL in a private browser window. Then your own Cloudflare session cannot hide problems with the sign-in step.
Test the full learner experience:
- Pass the sign-in step with an approved email address. Make sure that an unapproved address cannot get in.
- Start the first lesson from the home page.
- Use the Next, Previous, menu, restart and exit controls.
- Play at least one audio or video file to the end.
- Complete a quiz or exercise. Make sure that it shows the correct feedback.
- Open a deep lesson link directly in a new tab.
- Copy the URL of an image or PDF from the developer tools. Open it in a different private window. Make sure that it also asks for sign-in.
- Repeat the main path on a phone and in a different browser.
- Make sure that downloads open and that external links go to the correct destinations.
Also test the speed, especially on mobile connections. Google’s web performance audit guidance recommends that you look at the total page weight. It also recommends that you inspect unusually large files. In its example, one oversized image went from 349 KB to 17 KB, an improvement of about 95%. The authors resized the image and chose a more suitable format.
8. Share, Measure, Collect Feedback and Update
After the tests, send the course URL instead of the ZIP. Learners do not have to download or extract anything. You can put the link in an email, a client portal, a proposal or an onboarding document.
Without an LMS, you can measure only a few things:
- Cloudflare Access keeps authentication logs that show who signed in. Each entry has the email of the user, the application, the time and whether Access allowed the sign-in. You find them in the logs section of the Zero Trust dashboard under Access authentication logs, as the Access log documentation describes.
- For page views, you can turn on Cloudflare Web Analytics from the Metrics tab of the project. It reports page views and visitors for the site.
- For feedback, link a short form at the end of the course. You can make the form with a form service such as Google Forms or Tally. That needs no backend of your own.
A sign-in record shows that someone opened the course. It does not show that they understood it. If you need formal evidence of completion or compliance, use the assessment and reporting workflow of an LMS.
To update the course and keep the same link:
- Make changes in the source authoring tool.
- Export a complete new course folder.
- Test it locally.
- In Workers & Pages, open the project and select Create a new deployment. Choose production. Upload the whole folder. If you use Wrangler, run the same
pages deploycommand again. - Test the live URL again in a private window.
If a new version breaks something, open the deployments list of the project. On the last good deployment, choose Rollback to this deployment (rollback documentation). The Access policy stays attached to the address, so learners keep the same sign-in.
9. Troubleshoot Common Static HTML Course Hosting Failures
Most failures fit a few categories. Find the first failed request in the Network panel. Do not upload the same unchanged files again.
| Symptom | Likely cause | Practical fix |
|---|---|---|
| Images or styles are missing | Incorrect relative paths, changed folder structure or capitalization mismatch | Restore the exported structure and compare each requested URL with the uploaded file name |
| A missing file loads the course home page instead of an error | Cloudflare Pages treats a project with no 404.html as a single-page app (one HTML page that loads every route). It answers unknown paths with the home page |
Look at the response content in the Network panel, not only the status. If the course is not a single-page app, add a 404.html |
| The root address does not show the course | index.html is not at the root of the uploaded folder |
Upload the folder that directly contains index.html |
| Course loads slowly | Large video, audio, PNG or uncompressed assets | Resize images, compress media, remove unused files, or host long videos on a video service and embed them |
| Refreshing a lesson route fails on Netlify | The export is a single-page app that expects every route to load index.html |
Add /* /index.html 200 as the last line of a _redirects file (Netlify rewrite docs), or switch the exporter to hash routes such as #/lesson/2 |
| Quiz or completion data disappears | The package expects an LMS API, database or server endpoint | Publish through a compatible LMS or add a real application backend |
| Course works locally, but not online | Browser security rules or blocked cross-origin requests | Read the Console errors. Replace private, HTTP or local URLs with reachable HTTPS resources |
| Upload is rejected or stalls | More than 1,000 files for drag and drop, or a file over 25 MiB | Deploy with Wrangler. Remove source maps, authoring files, caches and duplicate media. Move large videos to a different place |
With single-page app routing, JavaScript can move a course from / to /lesson/2. If the learner refreshes that URL, the host looks for a file at that path. Cloudflare Pages handles this automatically when the project has no 404.html file, as its serving documentation explains. Netlify needs the rewrite rule in the table. GitHub Pages has no such setting, so hash routing is the simplest fix there.
No static host can fix a course that calls LMSInitialize, looks for a SCORM API, submits results to PHP or depends on server sessions. For such a course, choose a SCORM-compatible LMS, or an xAPI setup with a learning record store (a database of learner results). A custom application host is also an option.
Final Thoughts
A self-contained HTML course does not need a full LMS. Keep its structure. Put index.html at the root of the upload. Make sure that the relative paths work. Upload the complete folder, and then test it as a learner.
Cloudflare Pages serves the files at a stable address. Cloudflare Access limits the course to the people that you name, and it logs who signed in. If a shared password suits the group better, use Netlify or Vercel.
Use static hosting for browser content that needs only files and JavaScript. Use an LMS for enrollment, durable progress records, SCORM communication, scored assessments or certificates. Before you publish, decide which one you need. This prevents most surprises.
A docs portal with Ask AI
and support tickets
Connect a repository, or describe your product to start. Each docs site has search, Ask AI, an API console, an MCP server and support tickets.
- Publishes from GitHub on every push
- Works with Mintlify repositories
- API console from your OpenAPI file
- MCP server and llms.txt for AI agents
- Ask AI and support tickets
- Analytics for people and AI agents
Frequently Asked Questions
Can learners save their progress and continue later?
Yes, but only if the course saves progress in the browser itself, for example in local storage. Many HTML exports do this. That progress stays on one device and in one browser. If progress must follow a learner across devices, you need an LMS or a backend that stores it.
Can I host several courses on one domain?
Yes. Give each course its own Pages project and its own subdomain, such as safety.training.example.com and sales.training.example.com. With separate projects, you can update, roll back or restrict one course, and the other courses stay as they are.
Related Articles

Add a Custom Domain to a Cloudflare Pages Site
Add a custom domain or www to a Cloudflare Pages site: set it up in Pages first, add the DNS record, fix stuck verification and redirect www.

What Is localhost:3000?
Your browser uses localhost:3000 to reach an app running on your own computer. Sending that address to someone else does not give them access to your app.

Move Exported Grok Website Files to Another Host
Export a Grok Build site to GitHub, deploy it on Cloudflare Pages, Netlify, Vercel or GitHub Pages, test it, then retire the grok.me copy.