diff --git a/config/settings/base.py b/config/settings/base.py
index 074c1eab13383bfcd1b5ad15c7ca1356a4675118..070ecc8187223dabe9a55067fb812a28c92aabb5 100644
--- a/config/settings/base.py
+++ b/config/settings/base.py
@@ -687,3 +687,7 @@ PUBLIC_PAGE_RUNS = ['O4', 'ER15', 'O3']
 # Define how long to cache the public page:
 PUBLIC_PAGE_CACHING = int(get_from_env('DJANGO_PUBLIC_PAGE_CACHING',
     fail_if_not_found=False, default_value=300))
+
+# Define the number of results per page on the public page:
+PUBLIC_PAGE_RESULTS = int(get_from_env('DJANGO_PUBLIC_PAGE_RESULTS',
+    fail_if_not_found=False, default_value=15))
diff --git a/gracedb/superevents/templatetags/public_alerts.py b/gracedb/superevents/templatetags/public_alerts.py
index 9a35faccf0c21b2a4eac427f147628559b44ad18..7bb986da6b2371a47ee8085dd842626530eb34ab 100644
--- a/gracedb/superevents/templatetags/public_alerts.py
+++ b/gracedb/superevents/templatetags/public_alerts.py
@@ -24,7 +24,7 @@ def omegascan_links(superevent, run):
         return mark_safe(o3_deepomegascan_fmt.format(sid=superevent.superevent_id))
     else:
         link_list = []
-        for filename in superevent.log_set.filter(filename__contains='omegascan').order_by('filename').values_list('filename', flat=True):
+        for filename in sorted(set(superevent.log_set.filter(filename__contains='omegascan').order_by('filename').values_list('filename', flat=True))):
             # get the absolute uri for the file:
             file_url = build_absolute_uri(reverse('api:default:superevents:superevent-file-detail',
                            args=[superevent.superevent_id, filename]))
diff --git a/gracedb/superevents/views.py b/gracedb/superevents/views.py
index 4b17db12606e82b7a6d0069c9c08eb33ef02b811..90b9ca3d4bbf245a4269c552c2f1197a6900460b 100644
--- a/gracedb/superevents/views.py
+++ b/gracedb/superevents/views.py
@@ -4,11 +4,13 @@ from lal import gpstime
 
 from django.conf import settings
 from django.db.models import Q
-from django.http import Http404
-from django.shortcuts import redirect
+from django.http import Http404, JsonResponse
+from django.shortcuts import redirect, render
+from django.template.loader import render_to_string
 from django.urls import reverse
 from django.utils.decorators import method_decorator
 from django.views.decorators.cache import cache_page
+from django.views.decorators.vary import vary_on_headers
 from django.views.generic.detail import DetailView
 from django.views.generic import ListView
 
@@ -187,10 +189,13 @@ def public_alerts_redirect(request):
         run=settings.PUBLIC_PAGE_RUNS[0]))
 
 # The public alerts page:
+#@method_decorator(cache_page(0), name='dispatch')
 @method_decorator(cache_page(settings.PUBLIC_PAGE_CACHING), name='dispatch')
+@method_decorator(vary_on_headers('X-Requested-With'), name='dispatch')
 @method_decorator(public_if_public_access_allowed, name='dispatch')
 class SupereventPublic(DisplayFarMixin, ListView):
     model = Superevent
+    paginate_by = settings.PUBLIC_PAGE_RESULTS
     template_name = 'superevents/public_alerts.html'
     filter_permissions = ['superevents.view_superevent']
     log_view_permission = 'superevents.view_log'
@@ -200,8 +205,72 @@ class SupereventPublic(DisplayFarMixin, ListView):
     default_skymap_filename = 'bayestar.png'
     burst_skymap_filename = '{pipeline}.png'
     pe_results_tagname = 'pe_results'
+    show_button_text = 'Show All Public Events'
+    hide_button_text = 'Show Significant Events Only'
+
+    button_text_dict = {'show_button_text': show_button_text,
+            'hide_button_text': hide_button_text
+            }
+
+    # Override the standard get() method to allow for browser vs ajax requests.
+    def get(self, request, **args):
+        # For ajax requests, optionally get the "showall" value to to reveal
+        # insignificant events. Otherwise, for browser requests (the default when
+        # a user first lands on the public page), then only show significant.
+        allow_empty = self.get_allow_empty()
+
+        if not allow_empty:
+            # When pagination is enabled and object_list is a queryset,
+            # it's better to do a cheap query than to load the unpaginated
+            # queryset in memory.
+            if self.get_paginate_by(self.object_list) is not None and hasattr(
+                self.object_list, "exists"
+            ):
+                is_empty = not self.object_list.exists()
+            else:
+                is_empty = not self.object_list
+            if is_empty:
+                raise Http404(
+                    _("Empty list and “%(class_name)s.allow_empty” is False.")
+                    % {
+                        "class_name": self.__class__.__name__,
+                    }
+                )
+
+        showall = request.GET.get('showall', None)
+        if showall and showall.lower() in ['true', 't', '1', 'y']:
+            showall=True
+            button_text = self.hide_button_text
+        else:
+            showall=False
+            button_text = self.show_button_text
+
+        if request.headers.get('x-requested-with') == 'XMLHttpRequest':
+            self.object_list = self.get_queryset(showall)
+            context = self.get_context_data()
+            context['showall'] = int(showall)
+            context['button_text'] = button_text
+            rendered_table = render_to_string('superevents/public_alerts_table.html',
+                    context=context, request=request)
+            data = {'rendered_table': rendered_table}
+            return JsonResponse(data)
+        else:
+            self.object_list = self.get_queryset(showall)
+            context = self.get_context_data()
+            context['showall'] = int(showall)
+            context['button_text'] = button_text
+            return render(request, self.template_name, context=context)
+
 
-    def get_queryset(self, **kwargs):
+    # Modify get_queryset() to filter for significant vs insignificant:
+    def get_queryset(self, showall=False, **kwargs):
+        if showall:
+            return self.get_public_superevents()
+        else:
+            return self.significant_events(self.get_public_superevents())
+
+    # Get all publicly exposed production superevents:
+    def get_public_superevents(self, **kwargs):
         # Query only for public events for the given observation run.
         # if it's not in the run list, return a 404.
         self.obsrun = self.kwargs.get('obsrun')
@@ -214,7 +283,8 @@ class SupereventPublic(DisplayFarMixin, ListView):
             .prefetch_related('voevent_set', 'log_set')
         return qs
 
-    # Define insignificance per run:
+
+    # Define significance per run:
     def significant_events(self, sevents):
         # We're checking for ADVREQ|ADVOK|ADVNO
         # https://git.ligo.org/computing/gracedb/server/-/issues/303#note_725082
@@ -284,14 +354,16 @@ class SupereventPublic(DisplayFarMixin, ListView):
     def get_context_data(self, **kwargs):
         context = super(SupereventPublic, self).get_context_data(**kwargs)
 
+        # Get the total number of public events:
+        context['total_events'] = self.get_public_superevents().count()
+
         # For each superevent, get list of log messages and construct pastro
         # string
-        table_data = {}
         candidates = 0
         retractions = 0
 
         # get insignificant events
-        sig_events = self.significant_events(self.object_list)
+        sig_events = self.significant_events(self.get_public_superevents())
 
         # Filter and loop over exposed superevents for the given run:
         for se in self.object_list:
@@ -369,22 +441,40 @@ class SupereventPublic(DisplayFarMixin, ListView):
                 se.sourcetypes = ', '.join(sourcelist)
 
 
-        # Now add it to the output dict, with the key being the run value:
-        table_data['events'] =  self.object_list
-
-        total_events = self.object_list.count()
-
         # export the dictionary for rendering:
         context['signif_docs'] = self.insignificant_docs(self.obsrun)
-        context['data'] = table_data
+        context['events'] = self.object_list
         context['run'] = self.obsrun
-        context['total_events'] = self.object_list.count()
         context['total_sig'] = sig_events.count()
         context['total_insig'] = context['total_events'] - context['total_sig']
         context['candidates'] = candidates
         context['retractions'] = retractions
         context['sig_cands'] = context['total_sig'] - retractions
 
+        # export the button text:
+        context.update(self.button_text_dict)
+
+        # update pagination context:
+        page_size = self.get_paginate_by(self.object_list)
+        context_object_name = self.get_context_object_name(self.object_list)
+        if page_size:
+            paginator, page, self.object_list, is_paginated = self.paginate_queryset(
+                self.object_list, page_size
+            )
+            context.update({
+                "paginator": paginator,
+                "page_obj": page,
+                "is_paginated": is_paginated,
+                "object_list": self.object_list,
+            })
+        else:
+            context.update({
+                "paginator": None,
+                "page_obj": None,
+                "is_paginated": False,
+                "object_list": queryset,
+            })
+
         return context
 
 # FIXME: this is vestigial code from a planned curated view. I'm going 
diff --git a/gracedb/templates/superevents/public_alerts.html b/gracedb/templates/superevents/public_alerts.html
index 39b7f71fde5f0136f70db8a203a112e25f934952..1a5d7e1833c93b4b25ea67ecb41f66c544b2d9db 100644
--- a/gracedb/templates/superevents/public_alerts.html
+++ b/gracedb/templates/superevents/public_alerts.html
@@ -1,8 +1,4 @@
 {% extends "base.html" %}
-{% load cache %}
-{% cache 300 publicalerts %}
-{% load sanitize_html %}
-{% load logtags %}
 {% load static %}
 {% load public_alerts %}
 
@@ -18,9 +14,9 @@
 {% block pageid %}public{% endblock %}
 
 {% block jscript %}
-<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tablesaw/3.1.2/tablesaw.css" integrity="sha512-Gh62F+VzywHWpYtkkUqg2QmF2B67AeDmZv6uXznRUxXNUCFrRFlZRI2gKo2Bf3cgG5dAxAgxRJah3GgpJ0Z1Mw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
-<script src="https://cdnjs.cloudflare.com/ajax/libs/tablesaw/3.1.2/tablesaw.js" integrity="sha512-pJ/SPF4B6ZzEZp4b86/xpcZdcVkqtwyd/orBJQwOasT9S0qyIHcis2wRS/WGV4T4J8YrwqA/rPm+jDwQKkQs4Q==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
-<script src="https://cdnjs.cloudflare.com/ajax/libs/tablesaw/3.1.2/tablesaw-init.js" integrity="sha512-ct5fXUTmBz4voTyyxShWMjCpNKnAkw3zWIKETeQi8NBa9lPG6/Gf4b4+XvjB1kgaXzwM3e5R2EXF8J7+WsYu/w==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
+<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tablesaw/3.1.2/tablesaw.min.css" integrity="sha512-FW7xwIsGt3iCswNR3o4kcXqkKDvN8Q7obwdxFODRT+L6XkRtScLElfvobA7bjiC2iTgG/sRaBUqdrhqx0RTauw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
+<script src="https://cdnjs.cloudflare.com/ajax/libs/tablesaw/3.1.2/tablesaw.jquery.min.js" integrity="sha512-/Un6gjn0IXHJtR9j8VBYmN3ZoAVNMTQTufizEkeImLkb67lxsJcXv/cFswbOig2v5ykbGxX6zngh4u8ff7FJ0Q==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/tablesaw/3.1.2/tablesaw-init.min.js" integrity="sha512-w/1hLca9Fyn6iwnkMxUU55d/OlcA65RnP4v6kJ8NssnzHeHnJBeOzuHEkJ67zpt0ksiH1LQn8FKYZc/TT1O/aQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
 {% endblock %}
 
 {% block content %}
@@ -32,6 +28,7 @@
       <li>Retractions are marked in <span style="color:red;">red</span>. Retraction means that the candidate was manually vetted and is no longer considered a candidate of interest.</li>
       {% if signif_docs %}
       <li>Less-significant events are marked in <span style="background-color: #E5E4E2">grey</span>, and are not manually vetted. Consult the <a href=" {{ signif_docs }}" target="_blank">LVK Alerts User Guide</a> for more information on significance in {{ run }}.</li>
+      <li>Less-significant events are not shown by default. Press "<b>Show All Public Events</b>" to show significant and less-significant events.</li>
       {% endif %}
     </ul>
     <hr>
@@ -53,103 +50,63 @@
     {% if signif_docs %}
     <script>
       $(document).ready(function(){
-        $('#hideInsigButton').on('click', function () {
-          var text=$('#hideInsigButton').text();
-          if(text === 'Show Significant Events Only'){
-            $(this).html('Show All Public Events');
+        $('#showAllButton').on('click', function () {
+          var text=$('#showAllButton').text();
+          if(text === '{{ show_button_text }}'){
+            $(this).html('{{ hide_button_text }}');
+            $.ajax({
+              url: '{{ request.path }}',
+              type: "get",
+              cache: true,
+              dataType: 'html',
+              data : {
+                showall: "True"
+              },
+              success: function (data) {
+                var result = JSON.parse(data)
+                //alert(result.rendered_table)
+                $('#publicTableDiv').html(result.rendered_table);
+                $('#publicTable').trigger("enhance.tablesaw");
+              },
+              error: function (data) {
+                alert("Error rendering data");
+              }
+            });
           } else{
-            $(this).html('Show Significant Events Only');
+            $(this).html('{{ show_button_text }}');
+            $.ajax({
+              url: '{{ request.path }}',
+              type: "get",
+              cache: true,
+              dataType: 'html',
+              data : {
+                showall: "False"
+              },
+              success: function (data) {
+                var result = JSON.parse(data)
+                //alert(result.rendered_table)
+                $('#publicTableDiv').html(result.rendered_table);
+                $('#publicTable').trigger("enhance.tablesaw");
+              },
+              error: function (data) {
+                alert("Error rendering data");
+              }
+            });
           }
         });
       });
     </script>
 
-    <a class="btn btn-primary" role="button" data-toggle="collapse" href="#insignificant" aria-expanded="true" aria-controls="insignificant" id="hideInsigButton">Show Significant Events Only</a>
+    <a class="btn btn-primary" role="button" href="#" id="showAllButton">{{ button_text }}</a>
+
     <br>
     <br>
     {% endif %}
 
     {% if candidates > 0 or retractions > 0 %}
-    <div class="tablesaw-overflow">
-      <table class="tablesaw table-condensed table-resp-gracedb shadow p-3 mb-5 rounded" data-tablesaw-sortable data-tablesaw-sortable-switch data-tablesaw-mode="columntoggle" data-tablesaw-minimap>
-        <thead class="table-detail-th"><tr>
-            <th scope="col" data-tablesaw-sortable-col data-tablesaw-priority="persist">Event ID</th>
-            <th scope="col" data-tablesaw-sortable-col data-tablesaw-priority="1">Possible Source (Probability)</th>
-            {% if signif_docs %}
-              <th scope="col" data-tablesaw-sortable-col data-tablesaw-priority="1">Significant</th>
-            {% endif %}
-            <th scope="col" data-tablesaw-sortable-col data-tablesaw-sortable-string data-tablesaw-priority="2">UTC</th>
-            <th scope="col" data-tablesaw-col data-tablesaw-priority="3">GCN</th>
-            <th scope="col" data-tablesaw-col data-tablesaw-priority="4">Location</th>
-            <th scope="col" data-tablesaw-sortable-col data-tablesaw-priority="5">FAR</th>
-            <th scope="col" data-tablesaw-col data-tablesaw-priority="6">Comments</th>
-            <th scope="col" data-tablesaw-sortable-col data-tablesaw-priority="0">GPS</th>
-
-            {% if user_is_internal %}
-            <th scope="col" data-tablesaw-col data-tablesaw-priority="6">&Omega; Scan</th>
-            {% endif %}
-
-          </tr>
-        </thead>
-        <tbody>
-
-
-          {% for event in data.events %}
-
-          {% if event.retract %}
-          <tr style="background-color: #EDD;" {% if not event.signif %} class="collapse show" id="insignificant" {% endif %}>
-            {% elif not event.signif %}
-            <tr style="background-color: #E5E4E2" class="collapse show" id="insignificant">
-              {% endif %}
-
-              <td style="min-width: 100px;">
-                <a href="{% url "superevents:view" event.superevent_id %}">{{ event.default_superevent_id }}</a>
-              </td>
-              <td style="min-width: 100px;">{{ event.sourcetypes }}</td>
-              {% if signif_docs %}
-                <td style="min-width: 100px;">{% if event.signif %} Yes {% else %} No {% endif %}</td>
-              {% endif %}
-              <td style="min-width: 140px;">
-                <span style="font-size: 0px;">{{ event.t_0|floatformat:2 }}</span>{{ event.t_0_date }}<br/>
-                {{ event.t0_utc }} UTC
-              </td>
-              <td style="min-width: 110px;">
-                {% if run == "O3" %}
-                <a href={{ event.gcnurl }}>GCN Circulars</a><br/>
-                {% else %}
-                <a href={{ event.gcnurl }}>GCN Circular Query</a><br/>
-                {% endif %}
-
-                <a href={{ event.noticeurl }}>Notices</a> | <a href="{% url "legacy_apiweb:default:superevents:superevent-voevent-list" event.default_superevent_id %}">VOE</a>
-              </td>
-              <td>
-                {% if event.skymap_image %}
-                <a href="{{ event.skymap_image }}">
-                  <img src="{{ event.skymap_image }}" width="120px" />
-                </a>
-                {% else %}
-                No public skymap image found.
-                {% endif %}
-              </td>
-              <td style="min-width: 120px;">
-                <!-- Hidden float FAR value for table sorting -->
-                <span style="font-size: 0px;">{{ event.far_hz|floatformat:20 }}</span>{{ event.far_hr }}
-              </td>
-              <td style="min-width: 80px;">{{ event.comments|safe }} </td>
-              <td>{{ event.t_0|floatformat:2 }} </td>
-
-              {% if user_is_internal %}
-              <!-- Add Omega scans and PE results for LVC users -->
-              <td style="min-width: 70px;">
-                {{ event | omegascan_links:run }}
-              </td>
-              {% endif %}
-
-            </tr>
-            {% endfor %}
-        </tbody></table>
+    <div id="publicTableDiv">
+      {% include "superevents/public_alerts_table.html" %}
     </div>
-
     {% else %}
     <br />
     <h5 style='margin-left: 40px; font-weight: lighter;'>No {{ run }} candidates at present.</h5>
@@ -162,4 +119,3 @@
 </div>
 
 {% endblock %}
-{% endcache %}
diff --git a/gracedb/templates/superevents/public_alerts_table.html b/gracedb/templates/superevents/public_alerts_table.html
new file mode 100644
index 0000000000000000000000000000000000000000..6c553c134c898ad9775c6b7e02262ae9e2e1eb0f
--- /dev/null
+++ b/gracedb/templates/superevents/public_alerts_table.html
@@ -0,0 +1,98 @@
+{% load public_alerts %}
+
+<div class="pagination">
+  <span class="step-links" style="font-weight: lighter;">
+    {% if page_obj.has_previous %}
+    <a href="?page=1&showall={{ showall }}">&laquo; first</a>
+    <a href="?page={{ page_obj.previous_page_number }}&showall={{ showall }}">previous</a>
+    {% endif %}
+
+    <span class="current">
+      Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
+    </span>
+
+    {% if page_obj.has_next %}
+    <a href="?page={{ page_obj.next_page_number }}&showall={{ showall }}">next</a>
+    <a href="?page={{ page_obj.paginator.num_pages }}&showall={{ showall }}">last &raquo;</a>
+    {% endif %}
+  </span>
+</div>
+<div class="tablesaw-overflow">
+  <table class="tablesaw table-condensed table-resp-gracedb shadow p-3 mb-5 rounded" data-tablesaw-sortable data-tablesaw-sortable-switch data-tablesaw-mode="columntoggle" data-tablesaw-minimap id="publicTable">
+    <thead class="table-detail-th"><tr>
+        <th scope="col" data-tablesaw-sortable-col data-tablesaw-priority="persist">Event ID</th>
+        <th scope="col" data-tablesaw-sortable-col data-tablesaw-priority="1">Possible Source (Probability)</th>
+        {% if signif_docs %}
+        <th scope="col" data-tablesaw-sortable-col data-tablesaw-priority="1">Significant</th>
+        {% endif %}
+        <th scope="col" data-tablesaw-sortable-col data-tablesaw-sortable-string data-tablesaw-priority="2">UTC</th>
+        <th scope="col" data-tablesaw-col data-tablesaw-priority="3">GCN</th>
+        <th scope="col" data-tablesaw-col data-tablesaw-priority="4">Location</th>
+        <th scope="col" data-tablesaw-sortable-col data-tablesaw-priority="5">FAR</th>
+        <th scope="col" data-tablesaw-col data-tablesaw-priority="6">Comments</th>
+        <th scope="col" data-tablesaw-sortable-col data-tablesaw-priority="0">GPS</th>
+
+        {% if user_is_internal %}
+        <th scope="col" data-tablesaw-col data-tablesaw-priority="6">&Omega; Scan</th>
+        {% endif %}
+
+      </tr>
+    </thead>
+    <tbody>
+
+      {% for event in page_obj %}
+
+      {% if event.retract %}
+      <tr style="background-color: #EDD;" {% if not event.signif %} class="collapse show" id="insignificant" {% endif %}>
+        {% elif not event.signif %}
+        <tr style="background-color: #E5E4E2" class="collapse show" id="insignificant">
+          {% endif %}
+
+          <td style="min-width: 100px;">
+            <a href="{% url "superevents:view" event.superevent_id %}">{{ event.default_superevent_id }}</a>
+          </td>
+          <td style="min-width: 100px;">{{ event.sourcetypes }}</td>
+          {% if signif_docs %}
+          <td style="min-width: 100px;">{% if event.signif %} Yes {% else %} No {% endif %}</td>
+          {% endif %}
+          <td style="min-width: 140px;">
+            <span style="font-size: 0px;">{{ event.t_0|floatformat:2 }}</span>{{ event.t_0_date }}<br/>
+            {{ event.t0_utc }} UTC
+          </td>
+          <td style="min-width: 110px;">
+            {% if run == "O3" %}
+            <a href={{ event.gcnurl }}>GCN Circulars</a><br/>
+            {% else %}
+            <a href={{ event.gcnurl }}>GCN Circular Query</a><br/>
+            {% endif %}
+
+            <a href={{ event.noticeurl }}>Notices</a> | <a href="{% url "legacy_apiweb:default:superevents:superevent-voevent-list" event.default_superevent_id %}">VOE</a>
+          </td>
+          <td>
+            {% if event.skymap_image %}
+            <a href="{{ event.skymap_image }}">
+              <img src="{{ event.skymap_image }}" width="120px" />
+            </a>
+            {% else %}
+            No public skymap image found.
+            {% endif %}
+          </td>
+          <td style="min-width: 120px;">
+            <!-- Hidden float FAR value for table sorting -->
+            <span style="font-size: 0px;">{{ event.far_hz|floatformat:20 }}</span>{{ event.far_hr }}
+          </td>
+          <td style="min-width: 80px;">{{ event.comments|safe }} </td>
+          <td>{{ event.t_0|floatformat:2 }} </td>
+
+          {% if user_is_internal %}
+          <!-- Add Omega scans and PE results for LVC users -->
+          <td style="min-width: 70px;">
+            {{ event | omegascan_links:run }}
+          </td>
+          {% endif %}
+
+        </tr>
+        {% endfor %}
+    </tbody></table>
+</div>
+