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 now_ts = 'now' | date: "%s" -%}
{%- assign date_part = ss.reveal_after | date: "%Y-%m-%d" -%}
{%- assign hour = ss.reveal_hour | default: '00' -%}
{%- assign minute = ss.reveal_minute | default: '00' -%}
{%- assign reveal_datetime_str = date_part | append: ' ' | append: hour | append: ':' | append: minute -%}
{%- assign reveal_ts = reveal_datetime_str | date: "%s" -%}
{%- if now_ts >= reveal_ts -%}
{%- 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 only Shopify sections whose IDs start with 'shopify-section' and contain user-defined keywords.
{% 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 %}
Same here but for hiding 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 | date: '%Y-%m-%d' }}T{{ ss.reveal_hour | default: '00' }}:{{ ss.reveal_minute | default: '00' }}:00");
function updateCountdown() {
const now = new Date();
const timeDifference = targetDate - now;
if (timeDifference <= 0) {
// Countdown has expired — reload the page to reveal hidden sections
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;
if (label !== 'Days' && value < 10) {
numberElement.innerText = '0' + value;
} else {
numberElement.innerText = 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 on this date",
"info": "Use format: 2025-12-31",
"visible_if": "{{ section.settings.optionstab == 'dte' }}"
},
{
"type": "select",
"id": "reveal_hour",
"label": "Reveal Hour",
"default": "00",
"visible_if": "{{ section.settings.optionstab == 'dte' }}",
"options": [
{ "value": "00", "label": "00" },
{ "value": "01", "label": "01" },
{ "value": "02", "label": "02" },
{ "value": "03", "label": "03" },
{ "value": "04", "label": "04" },
{ "value": "05", "label": "05" },
{ "value": "06", "label": "06" },
{ "value": "07", "label": "07" },
{ "value": "08", "label": "08" },
{ "value": "09", "label": "09" },
{ "value": "10", "label": "10" },
{ "value": "11", "label": "11" },
{ "value": "12", "label": "12" },
{ "value": "13", "label": "13" },
{ "value": "14", "label": "14" },
{ "value": "15", "label": "15" },
{ "value": "16", "label": "16" },
{ "value": "17", "label": "17" },
{ "value": "18", "label": "18" },
{ "value": "19", "label": "19" },
{ "value": "20", "label": "20" },
{ "value": "21", "label": "21" },
{ "value": "22", "label": "22" },
{ "value": "23", "label": "23" }
]
},
{
"type": "select",
"id": "reveal_minute",
"label": "Reveal Minute",
"default": "00",
"visible_if": "{{ section.settings.optionstab == 'dte' }}",
"options": [
{ "value": "00", "label": "00" },
{ "value": "15", "label": "15" },
{ "value": "30", "label": "30" },
{ "value": "45", "label": "45" }
]
},
{
"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",
"default": true,
"label": "Still show liquid sections after countdown complete?",
"visible_if": "{{ section.settings.optionstab == 'liq' }}"
},
{
"type": "text",
"id": "my_keywords",
"default": "'product-grid', 'keyword2'",
"label": "Keywords to hide BEFORE date",
"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' find the whole id or part of it to hide those sections until after the date required.",
"visible_if": "{{ section.settings.optionstab == 'hid' }}"
},
{
"type": "paragraph",
"content": "For example if sectionID was 'shopify-section-template--25261002359163__product-grid' you could add the whole thing or (better) 'product-grid' to hide the section.",
"visible_if": "{{ section.settings.optionstab == 'hid' }}"
},
{
"type": "paragraph",
"content": "ID's may possibly change, but the name part is based on the liquid file name and so will not change.",
"visible_if": "{{ section.settings.optionstab == 'hid' }}"
},
{
"type": "paragraph",
"content": "Use this format: 'keyword1','keyword2' ",
"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.