Blog / How to build an SEO-friendly Black Friday countdown page in Shopify (updated)
How to build an SEO-friendly Black Friday countdown page in Shopify (updated)
Last year we shared a simple approach for building an evergreen Black Friday page that stays indexed all year and automatically switches to live deals when the sale starts. This update introduces a reusable Shopify section that does the same job with a clean editor UI - no theme edits required.
Why keep a single evergreen page
• Indexing and rankings build over time - keeping the same URL live improves the odds of appearing for Black Friday searches.
• Backlinks consolidate - links from previous years keep working for you.
• Data capture stays live - you can collect email sign-ups for early access without spinning up temporary pages.
• Launch operations are simpler - content switches automatically at the time you choose.
What’s new in this update
We’ve packaged the approach into a Black Friday countdown section you can add to your theme. It lets you:
• Show a countdown on your Black Friday collection page before launch.
• Hide specific sections before launch and reveal the product grid at the start time.
• Optionally hide different sections after launch.
• Style everything in the theme editor - fonts, colours, spacing, background images.
• Run it natively without third-party apps or external scripts.
How to set it up
1) Create a new section in your theme called blink-date-controller.liquid.
2) Paste the full code below.
3) In the theme customiser, add the section to your Black Friday collection template and move it to the very top so it renders first.
4) Set your reveal date, choose which sections to hide before and after, and style the countdown.
5) Publish when ready. The page will show the countdown until the reveal moment - then the product grid becomes visible automatically.
Note on time and timezone - the Liquid comparison below uses a date in YYYY-MM-DD format. The JavaScript countdown reads the same setting as a Date, which resolves in the shopper’s local timezone. If you need an exact hour for a specific timezone, consider using an ISO datetime such as 2025-11-28T00:00:01Z and adapting parsing to your needs.
The section code (copy and paste into sections/blink-date-controller.liquid)
{%- assign ss = section.settings -%}
{%- assign today = 'now' | date: "%Y-%m-%d" -%}
{%- assign reveal_date = ss.reveal_after | default: '2030-12-31' -%}
{%- if today >= reveal_date -%}
{%- assign show_content = true -%}
{%- assign show_countdown = false -%}
{%- else -%}
{%- assign show_content = false -%}
{%- assign show_countdown = true -%}
{%- endif -%}
{%- if show_countdown == true -%}
<style>
{% comment %}
Pre-hide Shopify sections whose IDs start with 'shopify-section' and contain user-defined keywords.
Add short, stable fragments like 'product-grid' rather than full IDs.
{% endcomment %}
{% assign keywords = ss.my_keywords | split: ',' %}
{% for keyword in keywords %}
[id^="shopify-section"][id*="{{ keyword | strip | remove: "'" | remove: '"' }}"] {
display: none !important;
}
{% endfor %}
</style>
{%- else -%}
<style>
{% comment %}
Hide a different set of sections once the sale is active.
{% endcomment %}
{% assign keywords = ss.my_keywords_2 | split: ',' %}
{% for keyword in keywords %}
[id^="shopify-section"][id*="{{ keyword | strip | remove: "'" | remove: '"' }}"] {
display: none !important;
}
{% endfor %}
</style>
{%- endif -%}
{%- if show_countdown == true -%}
{{ ss.my_liquid }}
{%- else -%}
{%- if ss.show_liquid == true -%}
{{ ss.my_liquid }}
{%- endif -%}
{%- endif -%}
{%- if show_countdown == true -%}
<div id="date-controller-{{ section.id }}" class="date-controller {% if ss.page_width == true %}{{ ss.page_width_class }}{% endif %}">
<div class="countdown-outer">
<div class="saletext">{{ ss.saletext }}</div>
<div id="countdown">
<div class="time-unit" data-label="Days">
<div class="digits">
<span class="number">00</span>
</div>
<div class="label">Days</div>
</div>
<div class="time-unit" data-label="Hours">
<div class="digits">
<span class="number">00</span>
</div>
<div class="label">Hrs</div>
</div>
<div class="time-unit" data-label="Minutes">
<div class="digits">
<span class="number">00</span>
</div>
<div class="label">Mins</div>
</div>
<div class="time-unit" data-label="Seconds">
<div class="digits">
<span class="number">00</span>
</div>
<div class="label">Secs</div>
</div>
</div>
</div>
<script>
const targetDate = new Date("{{ ss.reveal_after }}"); // Target date/time
function updateCountdown() {
const now = new Date();
const timeDifference = targetDate - now;
if (timeDifference <= 0) {
// Countdown expired - reload once to reveal hidden sections
if (!sessionStorage.getItem('bf_reloaded')) {
sessionStorage.setItem('bf_reloaded', '1');
location.reload();
}
return;
}
const days = Math.floor(timeDifference / (1000 * 60 * 60 * 24));
const hours = Math.floor((timeDifference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((timeDifference % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((timeDifference % (1000 * 60)) / 1000);
displayNumber('Days', days);
displayNumber('Hours', hours);
displayNumber('Minutes', minutes);
displayNumber('Seconds', seconds);
}
function displayNumber(label, value) {
const unit = document.querySelector(`.time-unit[data-label="${label}"]`);
const numberElement = unit?.querySelector('.number');
if (!numberElement) return;
numberElement.innerText = (label !== 'Days' && value < 10) ? ('0' + value) : value.toString();
}
updateCountdown(); // Run immediately on load
setInterval(updateCountdown, 1000);
</script>
</div>
{%- endif -%}
{%- if show_countdown == true -%}
{{ ss.my_liquid_2 }}
{%- else -%}
{%- if ss.show_liquid == true -%}
{{ ss.my_liquid_2 }}
{%- endif -%}
{%- endif -%}
{%- if show_countdown == true -%}
{%- assign bg_size_dsk = ss.background_size_dsk -%}
{%- case bg_size_dsk -%}
{%- when 'cover' -%}{%- assign bg_css_dsk = 'cover' -%}
{%- when 'contain' -%}{%- assign bg_css_dsk = 'contain' -%}
{%- when 'stretch' -%}{%- assign bg_css_dsk = '100% 100%' -%}
{%- else -%}{%- assign bg_css_dsk = 'cover' -%}
{%- endcase -%}
{%- assign bg_size_mob = ss.background_size_mob -%}
{%- case bg_size_mob -%}
{%- when 'cover' -%}{%- assign bg_css_mob = 'cover' -%}
{%- when 'contain' -%}{%- assign bg_css_mob = 'contain' -%}
{%- when 'stretch' -%}{%- assign bg_css_mob = '100% 100%' -%}
{%- else -%}{%- assign bg_css_mob = 'cover' -%}
{%- endcase -%}
<style>
#countdown {
display: flex;
justify-content: center;
gap: 10px;
}
.time-unit {
display: flex;
flex-direction: column;
align-items: center;
margin: 0;
}
.digits {
display: flex;
align-items: center;
justify-content: center;
background-color: {{ ss.digit_bak_col }};
width: 65px;
height: 55px;
margin-right: 4px;
text-align: center;
}
.number {
font-size: 33px;
font-weight: bold;
color: {{ ss.digit_txt_col }};
margin: 4px 0;
display: block;
}
.label {
margin-top: 10px;
text-transform: uppercase;
font-weight: bold;
color: {{ ss.section_txt_col }};
}
.saletext {
font-size: 22px;
font-weight: bold;
color: {{ ss.section_txt_col }};
text-align: center;
margin-bottom: 20px;
}
.countdown-outer {
{%- if ss.img_dsk -%}
background-image: url({{ ss.img_dsk | image_url }});
background-repeat: no-repeat;
background-position: center center;
background-size: {{ bg_css_dsk }};
{%- endif -%}
background-color: {{ ss.section_bak_col }};
height: {{ ss.height_dsk }}px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
}
@media only screen and (max-width: 800px) {
.countdown-outer {
{%- if ss.img_mob -%}
background-image: url({{ ss.img_mob | image_url }});
background-repeat: no-repeat;
background-position: center center;
background-size: {{ bg_css_mob }};
{%- endif -%}
height: {{ ss.height_mob }}px;
}
}
</style>
{%- endif -%}
{% schema %}
{
"name": "Blink Date Controller",
"settings": [
{
"type": "select",
"id": "optionstab",
"label": "\u200B",
"default": "sel",
"options": [
{ "value": "sel", "label": "Select tab" },
{ "value": "dte", "label": "Date Settings" },
{ "value": "cou", "label": "Countdown Settings" },
{ "value": "dsk", "label": "Desktop Settings" },
{ "value": "mob", "label": "Mobile Settings" },
{ "value": "hid", "label": "Hidden Sections Settings" },
{ "value": "liq", "label": "Liquid Section Settings" }
]
},
{
"type": "text",
"id": "reveal_after",
"label": "Reveal content after this date (YYYY-MM-DD or ISO datetime)",
"info": "Examples: 2025-11-28 or 2025-11-28T00:00:01Z",
"visible_if": "{{ section.settings.optionstab == 'dte' }}"
},
{
"type": "textarea",
"id": "saletext",
"label": "Countdown text",
"visible_if": "{{ section.settings.optionstab == 'cou' }}"
},
{
"type": "image_picker",
"id": "img_dsk",
"label": "Background Image",
"visible_if": "{{ section.settings.optionstab == 'dsk' }}"
},
{
"type": "select",
"id": "background_size_dsk",
"label": "Background Image Fit",
"default": "cover",
"visible_if": "{{ section.settings.optionstab == 'dsk' }}",
"options": [
{ "value": "cover", "label": "Cover" },
{ "value": "contain", "label": "Contain" },
{ "value": "stretch", "label": "Stretch (100% width & height)" }
]
},
{
"type": "text",
"id": "height_dsk",
"label": "Section Height (px)",
"default": "420",
"visible_if": "{{ section.settings.optionstab == 'dsk' }}"
},
{
"type": "image_picker",
"id": "img_mob",
"label": "Background Image",
"visible_if": "{{ section.settings.optionstab == 'mob' }}"
},
{
"type": "select",
"id": "background_size_mob",
"label": "Background Image Fit",
"default": "cover",
"visible_if": "{{ section.settings.optionstab == 'mob' }}",
"options": [
{ "value": "cover", "label": "Cover" },
{ "value": "contain", "label": "Contain" },
{ "value": "stretch", "label": "Stretch (100% width & height)" }
]
},
{
"type": "text",
"id": "height_mob",
"label": "Section Height (px)",
"default": "420",
"visible_if": "{{ section.settings.optionstab == 'mob' }}"
},
{
"type": "color",
"id": "section_bak_col",
"label": "Section Background Colour",
"default": "#ddd",
"visible_if": "{{ section.settings.optionstab == 'cou' }}"
},
{
"type": "color",
"id": "section_txt_col",
"label": "Section Text Colour",
"default": "#000",
"visible_if": "{{ section.settings.optionstab == 'cou' }}"
},
{
"type": "color",
"id": "digit_bak_col",
"label": "Digit Background Colour",
"default": "#000",
"visible_if": "{{ section.settings.optionstab == 'cou' }}"
},
{
"type": "color",
"id": "digit_txt_col",
"label": "Digit Text Colour",
"default": "#fff",
"visible_if": "{{ section.settings.optionstab == 'cou' }}"
},
{
"type": "checkbox",
"id": "page_width",
"label": "Constrain Width",
"default": false,
"visible_if": "{{ section.settings.optionstab == 'cou' }}"
},
{
"type": "text",
"id": "page_width_class",
"label": "Constrain width class",
"default": "page-width",
"visible_if": "{{ section.settings.optionstab == 'cou' }}"
},
{
"type": "liquid",
"id": "my_liquid",
"label": "Liquid/HTML Above Countdown",
"visible_if": "{{ section.settings.optionstab == 'liq' }}"
},
{
"type": "liquid",
"id": "my_liquid_2",
"label": "Liquid/HTML Below Countdown",
"visible_if": "{{ section.settings.optionstab == 'liq' }}"
},
{
"type": "checkbox",
"id": "show_liquid",
"label": "Still show liquid sections after countdown complete?",
"default": true,
"visible_if": "{{ section.settings.optionstab == 'liq' }}"
},
{
"type": "text",
"id": "my_keywords",
"label": "Keywords to hide BEFORE date",
"default": "'product-grid','keyword2'",
"visible_if": "{{ section.settings.optionstab == 'hid' }}"
},
{
"type": "text",
"id": "my_keywords_2",
"label": "Keywords to hide AFTER date",
"visible_if": "{{ section.settings.optionstab == 'hid' }}"
},
{
"type": "paragraph",
"content": "Use view source and search for 'shopify-section' to find the whole id or a stable fragment to hide until the reveal time.",
"visible_if": "{{ section.settings.optionstab == 'hid' }}"
},
{
"type": "paragraph",
"content": "Example: if the section id is 'shopify-section-template--25261002359163__product-grid' add 'product-grid'.",
"visible_if": "{{ section.settings.optionstab == 'hid' }}"
},
{
"type": "paragraph",
"content": "IDs can vary per template, but the name fragment is based on the Liquid file name and is stable.",
"visible_if": "{{ section.settings.optionstab == 'hid' }}"
},
{
"type": "paragraph",
"content": "Format examples: 'product-grid','promo-banner'",
"visible_if": "{{ section.settings.optionstab == 'hid' }}"
}
],
"presets": [
{
"name": "Blink Date Controller",
"category": "BlinkSEO Sections"
}
]
}
{% endschema %}
Practical tips
• Place the section first - it needs to render before the product grid to avoid brief flashes of hidden content.
• Choose specific keywords - short, unique fragments like product-grid are less likely to hide the wrong section.
• Test early - preview on a duplicate template and move the reveal time forward for a quick dry run.
• Consider caching - if you use CDN rules, test reveal behaviour a few minutes ahead of time.