How to Create a Course in LifterLMS: From Blank Page to Published
Build one LifterLMS course from start to finish: the course post, Course Options, the outline, lesson content, a quiz, an access plan, publishing…
Read articleMost WordPress speed advice assumes that visitors are anonymous and that every page can be saved once and served to everyone. An LMS breaks that assumption. Your most important visitors are logged in, and nearly every page they open is built for them alone.
That is why a LifterLMS site can score well on its public sales page and still feel slow to a student halfway through a course. This guide separates the two problems and works through each layer in the order that usually pays off.
A page cache stores finished HTML and skips PHP and the database on later visits. That only works when the HTML is the same for everyone. A lesson page is not. LifterLMS checks whether this user is enrolled, whether drip or prerequisite rules allow access and what they have completed, then outputs progress, navigation and a completion button that depend on the answers.
Serve a cached copy to the wrong person and you get the familiar symptoms: a student who paid but sees the enrollment prompt, progress that does not update, or a dashboard that shows someone else's name. Logged-in pages have to be generated per request. The goal shifts from avoiding PHP to making PHP and the database fast.
| Page | Page cache? | Why |
|---|---|---|
| Home, landing pages, blog | Yes | Same for every anonymous visitor |
| Course and membership catalogs, course sales pages | Yes, for logged-out visitors only | Students see progress and enrollment state |
| Lessons | Not for logged-in students | Access and progress are per student |
| Quizzes | Never | Attempts are live data |
| Student Dashboard | Never | Entirely personal |
| Checkout | Never | Orders, coupons and payment forms |
LifterLMS does part of this for you. On the Checkout page, the Student Dashboard page and quiz pages it sends no-cache headers and defines the DONOTCACHEPAGE constant that many caching plugins respect. It does not do this for lessons or courses, so those depend on your cache skipping logged-in users. Confirm that setting in your caching plugin, then confirm that any server-level or CDN cache in front of WordPress follows the same rule. The LifterLMS caching FAQ has notes for specific caching plugins and Cloudflare.
Anonymous traffic stays cacheable. Current versions of LifterLMS only set the session cookie, whose name starts with wp_llms_session_, once there is something to remember, such as an applied coupon. Older versions gave it to every visitor, which made some page caches skip those views.
If you build your own page with per-student content, add it to the list LifterLMS protects:
add_filter( 'llms_no_cache_page_ids', 'acme_no_cache_pages' );
function acme_no_cache_pages( $ids ) {
$ids[] = 1234; // ID of a custom page with per-student content.
return $ids;
}
Since logged-in pages must be generated, reduce what each one costs. A persistent object cache such as Redis or Memcached keeps the results of repeated database lookups in memory between requests. WordPress Site Health recommends one on sites it judges would benefit.
LifterLMS uses the WordPress object cache for lookups such as enrollment status, so with a persistent backend those checks stop hitting the database on every page view. Run a current LifterLMS version if you turn this on. Recent releases fixed several stale-value problems that only appear with a persistent cache.
Student activity lives in LifterLMS tables that grow with every enrollment, lesson and quiz:
{prefix}lifterlms_user_postmeta holds enrollment and completion records. It is indexed by user ID and post ID, not by meta key, so custom queries should always filter on one of those.{prefix}lifterlms_quiz_attempts holds one row per attempt, including the questions and answers.{prefix}lifterlms_events records sessions and page activity for logged-in students.What keeps them fast:
LLMS_Student_Query, avoid queries inside loops, and use Query Monitor on staging to see slow and duplicate queries grouped by the plugin responsible.Uncached requests need CPU, so hosting matters more for an LMS than for a brochure site. The plugin's WordPress.org listing sets a low minimum, but the LifterLMS system requirements recommend considerably newer versions: PHP 8.3 and MySQL 8.0 or MariaDB 10.6 at the time of writing. The same page warns that a host caching dynamic pages such as the Student Dashboard will stop the site from working correctly. Questions to ask a host:
Do not serve lesson video from your own server. Video files eat bandwidth and compete with students for server resources. LifterLMS embeds video through WordPress oEmbed, and its documentation points to dedicated hosts such as Vimeo, Wistia and YouTube. On sales pages, consider a click-to-load preview image so the player script is not downloaded until someone presses play.
For images, upload at the size they are displayed, use a modern format, and lazy-load what sits below the fold. Do not lazy-load the main image at the top of a course sales page. It is usually the Largest Contentful Paint element, and delaying it makes that score worse.
LifterLMS loads its core stylesheet and scripts, including a few jQuery UI components, on every front-end page, not only on LMS pages. That is reasonable for a plugin whose blocks and shortcodes can appear anywhere, but it means your marketing pages carry LMS assets. Removing them selectively is possible and easy to get wrong. Do it from a child theme or small plugin, as described in how to customize LifterLMS without breaking updates, and test on staging with logged-in and logged-out users.
Lesson time tracking is another cost to know about. When a lesson has a minimum time requirement, or tracking is switched on for all lessons under LifterLMS > Settings > Courses, each student with a lesson open sends a small background request every 30 seconds by default. Those requests cannot be cached, so enable tracking where you need it and not everywhere.
The larger savings are usually outside LifterLMS: page builders, sliders, chat widgets, analytics tags and plugins nobody remembers installing. Remove what you do not use.
| Metric | Good | What it measures |
|---|---|---|
| Largest Contentful Paint (LCP) | 2.5 seconds or less | Loading |
| Interaction to Next Paint (INP) | 200 milliseconds or less | Responsiveness |
| Cumulative Layout Shift (CLS) | 0.1 or less | Visual stability |
These are Google's Core Web Vitals thresholds, judged at the 75th percentile of page loads on mobile and desktop separately. Time to First Byte is not a Core Web Vital, but web.dev suggests 0.8 seconds or less as a rough guide, and on logged-in LMS pages it is usually the first number to check.
Measure before you change anything:
Work in this order: confirm cache exclusions are correct, add a persistent object cache, update PHP and LifterLMS, move video off the server, trim front-end weight, then look at the database. The first two deal with the classic complaints: students seeing the wrong content, and a slow dashboard.
Bring in help when the site slows down as students log in together, when checkout times out, or when you suspect custom code and cannot prove it. Our LifterLMS optimization service starts with measurement, and if the cause turns out to be custom code, our LifterLMS development guide explains what upgrade-safe fixes look like. You can also get in touch with a slow URL and a description of what students are experiencing.
Build one LifterLMS course from start to finish: the course post, Course Options, the outline, lesson content, a quiz, an access plan, publishing…
Read article
Know what every control in the LifterLMS Course Builder does: section and lesson actions, each field in the lesson settings panel, the quiz editor…
Read article
Decide how many sections you need, how big a lesson should be, whether order is enforced, which lessons to open as free previews, and where quizzes…
Read articleSkip the trial and error. Get specialist LifterLMS help and ship faster.