Posts

Showing posts with the label Cloning the Mediumish Theme from GitHub

how can i publish mediumish theme on github pages

table of contents

Why use Mediumish on GitHub Pages

The Mediumish theme is well-known for its clean and professional design that makes content easy to read and visually appealing. Its minimalist style focuses the reader’s attention on the writing itself, without unnecessary distractions. This makes it a perfect choice for blogs, portfolios, and even documentation pages.

When you combine Mediumish with GitHub Pages, you get a free hosting solution that is reliable and fast. GitHub Pages serves your site from a global content delivery network, meaning visitors can access your content quickly from anywhere in the world. This is especially valuable for audiences spread across different countries.

Another reason to use this combination is simplicity. You don’t have to worry about setting up complex servers or paying monthly hosting fees. Everything is managed directly from your GitHub repository, which doubles as your backup. Every change you make is stored in version control, so you can roll back if something goes wrong.

Finally, hosting on GitHub Pages provides automatic HTTPS support, giving your site a professional and secure image. This can help boost trust with your readers and improve your ranking in search engines.

Quick overview

Before diving into the technical steps, it’s helpful to have a clear roadmap of the process. Deploying a theme like Mediumish to GitHub Pages involves setting it up locally, adjusting its configuration, pushing it to GitHub, and then activating GitHub Pages in the repository settings.

First, you will need to download or clone the theme files to your local machine. Then, you’ll make sure the theme runs properly locally before moving on to deployment. This step is crucial because any errors found early can be fixed quickly without affecting a live site.

Once the theme is ready, you will choose one of several deployment methods: direct upload, GitHub Actions automation, or manually pushing to a special branch. Each has its pros and cons, which we will discuss in detail later.

After deployment, you might need to fix broken paths for images or styles. You may also want to connect a custom domain and enable HTTPS. We’ll also cover common problems you might face and how to fix them effectively.

What you need before starting

Before starting the deployment process, make sure you have everything you need. Skipping these preparations can lead to delays or errors later in the process.

You will need a GitHub account. If you don’t already have one, you can sign up for free on GitHub’s official website. Once your account is ready, make sure you have Git installed on your computer so you can manage files and push updates to your repository.

Next, you need the Mediumish theme files. You can either download them from a trusted source or clone them from a Git repository. If your version of Mediumish is based on Jekyll or another static site generator, you’ll also need to have that tool installed on your computer.

While not required, basic knowledge of HTML and CSS will help you customize the theme later. If you know how to use a text editor like VS Code or Sublime Text, the process will be much smoother.

Prepare the theme on your computer

Setting up the theme locally ensures that it works correctly before you publish it. This step allows you to preview your site and make changes without affecting visitors.

Start by downloading the Mediumish theme into a dedicated folder. Organize the files so they are easy to manage. Delete any placeholder content you don’t need, such as example posts or unused images.

Once your files are ready, you can preview the site locally. If your theme is pure HTML and CSS, simply open the index.html file in your browser. If it requires Jekyll or another static site generator, you’ll need to run the build command in your terminal and open the local server link provided.

By testing locally, you can catch design issues, missing images, or broken links early. This saves time and prevents embarrassing mistakes on your live site.

example folder layout

path purpose
index.htmlMain homepage file
assets/cssStylesheets for layout and design
assets/jsJavaScript for interactivity
imagesImage files used in the theme
_postsPost source files if using Jekyll

Choose your deployment method

GitHub Pages offers multiple ways to publish your site. The method you choose will depend on the complexity of your theme and your workflow preferences.

Method A Direct upload to GitHub Pages

This is the simplest method for static HTML versions of Mediumish. You create a new GitHub repository, upload your files through the GitHub interface, and activate GitHub Pages in the settings. The site goes live almost immediately.

While easy, this method can be slow if you need to make frequent updates because you have to manually upload files each time. However, it’s great for beginners or for sites that don’t change often.

Method B Deploy using GitHub Actions

GitHub Actions allows you to automate the deployment process. Whenever you push changes to your repository, GitHub will automatically build and publish your site based on the workflow file you set up. This is ideal if your theme needs preprocessing steps like compiling Sass or running a static site generator.

Automation saves time and ensures consistency, making it a great choice for regular bloggers or developers who frequently update their sites.

Method C Push to gh-pages branch

With this method, you build your site locally and push the generated files to a special branch called gh-pages. GitHub Pages will serve the content of that branch directly. This method gives you full control over the build process but requires you to remember to build and push manually each time.

After your site is live, you might notice some images or CSS files not loading correctly. This is often caused by incorrect file paths. GitHub Pages serves project sites from a subdirectory, so absolute paths pointing to the root might break.

To fix this, use relative paths or configure your site’s base URL properly. For example, in Jekyll, you can set the baseurl in your configuration file. Always test links locally before pushing updates to avoid downtime.

Developer tools in your browser can help identify broken assets. Open the console or network tab, refresh the page, and look for errors. Once you find the problematic paths, correct them and redeploy.

Set up custom domain and HTTPS

Connecting a custom domain makes your site more memorable and professional. You can register a domain through a provider like Namecheap or Google Domains, then point it to your GitHub Pages site by adjusting DNS settings.

Once your domain is set up, GitHub Pages offers free HTTPS certificates. Enabling HTTPS ensures data between your site and visitors is encrypted, which increases trust and can improve SEO.

example dns setup

For example.com, point A records to GitHub Pages IP addresses and set a CNAME record for www pointing to username.github.io.

Common issues and how to fix them

Even with careful setup, you might run into issues. Common problems include broken links, missing images, and site not updating after changes. These are usually caused by incorrect paths, caching, or pushing files to the wrong branch.

Clearing your browser cache, verifying your GitHub Pages settings, and checking your repository structure can resolve most issues quickly.

quick troubleshooting checklist
  1. Check if index.html is in the correct publishing branch.
  2. Verify that all CSS and JS paths are correct.
  3. Test your site with a local server before pushing.
  4. Update baseurl if you are using a project site.

Keeping your site up to date

Once your site is live, keeping it updated is important for security, SEO, and user experience. Add new posts or pages regularly to keep readers engaged. Review your theme’s GitHub repository for updates or bug fixes you can apply.

It’s also wise to periodically check your site on different devices to ensure it looks good everywhere. Mobile responsiveness is especially important for modern web traffic.

How do I add multilingual support to the Mediumish theme

This guide shows how to extend the Mediumish theme to support multiple languages. It is written for beginners and focuses on practical, evergreen techniques that work for static site generators like Jekyll and hosting on GitHub Pages or Netlify. You will get clear options, code snippets, a language switcher example, SEO recommendations, and a short comparison table to help you choose the best approach for your site.

Table of contents

Why add multilingual support

Adding multilingual support expands your audience, improves user experience, and can increase traffic from search engines that target different languages. For a Mediumish-based blog, multilingual support means your templates, navigation, and posts are aware of the current language and display the correct labels, translated menus, and content versions.

There are two key goals when implementing multilingual support:

  1. Present content in the visitor's preferred language in a clear and consistent URL structure.
  2. Keep content discoverable by search engines using proper SEO tags and structured data.

Common strategies explained

There are several ways to implement multilingual support. Each has tradeoffs in complexity, maintainability, and SEO. Choose based on site size, number of languages, and how you manage translations.

Single repo, language folders

Create language subfolders at the top level like /en/ and /id/. Each language contains translated posts and localized templates. This approach is simple and works well with Jekyll by leveraging permalinks and collections.

Single repo, language front matter

Use a single post file per language pair and mark language in front matter. Example front matter keys: lang, translation_of. Your templates filter posts by language. This reduces file duplication but requires careful linking between translations.

Multiple repos or branches

Maintain a separate repository or branch per language. This isolates translations but adds overhead for synchronization and maintenance. Not recommended for small blogs.

Client side translations

Load translations with JavaScript and keep a single source of content. This is fast for small UI text changes but not ideal for full post translations since search engines may not index dynamically injected post content.

Recommended file structure

This structure is practical for Mediumish extended with multilingual support. It keeps posts and page translations organized.

# example site structure
/
├─ _config.yml
├─ _data/
│  └─ locales.yml
├─ _includes/
│  └─ lang-switcher.html
├─ _layouts/
│  └─ default.html
├─ _posts/
│  ├─ en/
│  │  └─ 2025-01-12-welcome-en.md
│  └─ id/
│     └─ 2025-01-12-welcome-id.md
├─ index.html
└─ assets/

Using _posts/en and _posts/id keeps translations grouped. Alternatively you may keep all posts in _posts with front matter key lang. Use whichever fits your workflow.

Config and data examples

Keep language metadata in a data file so templates can loop languages and labels are centralized. Example _data/locales.yml:

# _data/locales.yml
en:
  code: en
  name: English
  path: /en
  date_format: "%B %d, %Y"
  site_title: "My Blog"
  nav:
    home: "Home"
    about: "About"
id:
  code: id
  name: "Bahasa Indonesia"
  path: /id
  date_format: "%d %B %Y"
  site_title: "Blog Saya"
  nav:
    home: "Beranda"
    about: "Tentang"

Then add minimal config to _config.yml:

# _config.yml
title: "My Mediumish Site"
baseurl: ""
url: "https://example.com"
languages:
  - en
  - id
default_lang: en

The languages array ensures you can iterate languages in templates when generating language switchers and hreflang tags.

Liquid language switcher

Add an include that renders language links. It reads the locales.yml data and current page language to build links. Save this as _includes/lang-switcher.html.

{% raw %}{% assign current_lang = page.lang | default: site.default_lang %}
{% endraw %}

Note about the snippet above: it shows intent how to build URLs. You may need to adjust translated_url logic depending on whether posts are in language-specific folders or marked by front matter. If you use language folders as in the recommended structure, build links using post slugs and replace language segment accordingly.

Translating posts and content workflow

There are two common approaches to post translations:

  • Parallel posts Create a translated post file for each language under language folders. Keep the date and slug consistent so the posts appear as translations of one another.
  • Single master with translation references Keep one canonical post and add translation meta in front matter that links to other language versions. Your templates use the links to provide translated versions.

Example front matter for a translated post using parallel files:

---
title: "Welcome to the blog"
lang: en
translation_key: welcome-2025
---
---
title: "Selamat datang di blog"
lang: id
translation_key: welcome-2025
---

Using a consistent translation_key allows templates to find sibling translations across languages. Example search in Liquid:

{% raw %}{% assign siblings = site.posts | where: "translation_key", page.translation_key %}
{% for p in siblings %}
  {% if p.lang != page.lang %}
    {{ p.title }} ({{ p.lang }})
  {% endif %}
{% endfor %}{% endraw %}

SEO and hreflang best practices

Search engines expect consistent URL structures and hreflang annotations to identify language variants. Implement the following:

  • Unique URL per language, e.g. https://example.com/en/my-post and https://example.com/id/my-post.
  • Use rel="alternate" hreflang="xx" links in the <head> for all language versions and a hreflang="x-default" fallback if needed.
  • Include lang attributes on the <html> tag for each language layout.
  • Translate meta title and description and include language in structured data where relevant.

Liquid example to generate hreflang tags from languages array (place inside your default layout head):

{% raw %}{% for code in site.languages %}
  {% assign locale = site.data.locales[code] %}
  
{% endfor %}
{% endraw %}

Remember to test hreflang with Google Search Console after deployment and ensure each language page returns a 200 status code with correct canonical tags.

Automation and build tips

For sites with many posts and translators, automation helps. Use these tips:

  • Store translated strings for UI in _data/locales.yml so you only edit text in one place.
  • Use scripts to generate translated post skeletons. For example a small Node or Ruby script reads existing posts and creates translated markdown files with the translation_key set. This saves repetitive work.
  • Use continuous integration on Netlify or GitHub Actions to validate that translations are linked and that no broken links exist after builds.

Practical code example

Below is a compact example showing one way to set up a translated post and a simple language-aware post layout.

--- 
# file: _posts/en/2025-01-12-example-en.md
title: "Example post in English"
lang: en
translation_key: example-2025
---

This is the English version.

--- 
# file: _posts/id/2025-01-12-example-id.md
title: "Contoh tulisan dalam Bahasa"
lang: id
translation_key: example-2025
---

Ini versi Bahasa Indonesia.

Now an excerpt of a language-aware post.html layout using Liquid:

{% raw %}


  
  {{ page.title }}
  
  {% include seo-hreflang.html %}


  
{% include lang-switcher.html %}

{{ page.title }}

{{ page.date | date: site.data.locales[page.lang].date_format }}

{{ content }}

© {{ site.time | date: "%Y" }} {{ site.data.locales[page.lang].site_title }}

{% endraw %}

Adjust the include names to your theme and ensure includes exist for seo-hreflang.html and lang-switcher.html.

Strategy comparison table

Strategy Pros Cons Best for
Language folders Clear URLs, SEO friendly, straightforward Duplicate structure, more files Small to medium blogs
Single files with lang key Less duplication, centralized Templates must filter, linking translations is manual Sites with few posts and many UI strings
Client side translations Easy UI localization, less build complexity Poor SEO for post content, requires JS UI strings only, not full posts

FAQ

How should I handle date formats per language

Keep date formats in your locale data file and use Liquid date formatting per page language. This keeps dates natural for readers and consistent across templates.

How do I avoid duplicate content penalties

Use hreflang tags, unique translated meta descriptions, and correct canonical tags. Search engines recognize language variants when properly annotated with hreflang and language-specific URLs.

Can I keep Mediumish styles while adding languages

Yes. Keep the theme layouts and add language-aware includes and data. Avoid heavy inline styles. The layout logic changes are minimal: load labels from locale data and resolve translated content files.

Finally, a few practical deployment reminders:

  • Test language switcher links locally before pushing to production.
  • Use a staging site to verify hreflang and robots handling for each language.
  • Keep translators or translation memory tools organized so updates are easy to track.

Extending Mediumish for multiple languages is mostly about planning your content structure, centralizing UI strings, and adding a few Liquid helpers for switching languages and generating hreflang tags. The code examples here are intentionally simple so you can adapt them to your exact Mediumish setup.

How to tweak Mediumish theme appearance for better branding

Why tweak Mediumish

A cloned Mediumish theme gives you a solid, minimalist starting point that looks modern out of the box. However, default themes are intentionally generic. Tweaking colors fonts and the layout helps your blog reflect your brand voice improve readability and make a memorable first impression. Small changes often deliver disproportionate benefits: clearer headings better contrast and consistent typography instantly feel more professional.

Planning your branding

Before editing files you need a short plan. Good planning saves time and avoids rollback loops. Keep the plan small and actionable: pick one primary color one accent color and two fonts one for headings and one for body text. Decide if you want a constrained content width or a wider reading column. Note any content types that need custom styles such as code blocks quotes or featured images.

Choosing a color palette

A functional palette has three roles: brand color for emphasis interactive color for links and buttons and neutral tones for backgrounds and text. Prioritize contrast for body text and links to keep accessibility good. Test palette choices on the actual site mockup rather than relying solely on swatches.

Selecting typography

Typography choices affect perceived tone. Sans serif conveys modern and clean while serif may feel classic or editorial. For readability pick a size between 16 and 18 pixels for body text use a line-height around 1.5 and limit the number of typeface families to two. Always include system font fallbacks for speed and reliability.

Safe workflow for a cloned theme

Work safely by creating a local branch or a copy of the theme files before changing anything. If you use GitHub Pages or a similar hosting platform keep changes in a branch and preview them before merging to the production branch. If the cloned theme has a build step note where the compiled CSS is generated and modify source files not compiled output. Keep a short changelog with a list of edited files and CSS variables you updated.

Practical CSS changes

This section focuses on actionable edits you can perform quickly. Most Mediumish clones use a single stylesheet or a small set of modular styles. Target high impact areas first: CSS variables colors fonts and container sizes.

Using CSS variables

If the theme already uses CSS variables you can centralize your changes in one place. If not you can add a :root block at the top of the main stylesheet and reference the variables throughout the CSS. This approach makes future brand updates trivial because values are changed in one file.

Example CSS variables to add
:root {
  --brand-primary: #2b6cb0;
  --brand-accent: #f6ad55;
  --text-main: #222222;
  --text-muted: #6b7280;
  --bg-page: #ffffff;
  --max-width: 760px;
  --base-font-size: 16px;
  --heading-font: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;
  --body-font: "Source Sans Pro", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;
}
  

Changing colors

Swap colors by updating the variables above or by editing the specific color rules used for links buttons and headings. Target these selectors first:

  • a, a:visited
  • button and .btn classes
  • .site-header .brand or .logo
  • article headings and blockquote borders

Use the mix-blend-mode cautiously and avoid low contrast text on busy backgrounds. For interactive elements add subtle hover states like a darker shade on hover or a gentle transform scale to improve perceived responsiveness.

Quick color mapping table

Intent Variable Example use
Primary brand --brand-primary CTA buttons link accents
Accent --brand-accent Badges highlighted text
Body text --text-main Paragraphs and captions
Muted text --text-muted Meta dates author names

Font setup and fallbacks

Use a reliable CDN for web fonts or self host the fonts if you prefer privacy and speed. Add font-display swap to avoid invisible text. Example import for Google Fonts looks like this in the head of the template:

Font link snippet to include


  

Then use the variables to apply fonts:

body {
  font-family: var(--body-font);
  font-size: var(--base-font-size);
  line-height: 1.6;
  color: var(--text-main);
}

h2,h3,h4 {
  font-family: var(--heading-font);
  line-height: 1.2;
  margin-top: 1.2rem;
}

Layout tweaks

Layout changes yield a noticeable effect. Common layout tweaks include adjusting the content max width controlling image size alignment and changing the sidebar behavior on mobile. Use a container rule to constrain readable text width. A typical readable measure for longform content is between 60 and 75 characters per line. Translate that to max-width for your font and size.

Example container CSS
.container {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 1.5rem;
}
.featured-image {
  max-width: 100%;
  height: auto;
  display: block;
  margin: 1rem 0;
}
@media (min-width: 980px) {
  .content + .sidebar {
    width: 300px;
  }
  .content {
    width: calc(100% - 320px);
  }
}
  

If the cloned theme contains utility classes adjust them sparingly to avoid breaking components that expect the original sizing. For major layout changes prefer adding new classes rather than altering the original ones so you can revert easily.

Accessibility and performance

Good design is inclusive and fast. Some quick checks:

  • Ensure text contrast meets WCAG AA minimums for body text and headings.
  • Add lang attribute to the HTML element in your template for better screen reader behavior and search engine clarity.
  • Use semantic markup for article time author and headings for screen readers and SEO.
  • Lazy load offscreen images and set width and height attributes on images to avoid layout shifts.
  • Minify CSS and combine critical CSS inline if hosting allows it to improve first contentful paint.

Testing and deploying changes

Test on several devices and browsers. Use browser devtools to emulate network throttling and a few screen sizes. Run automated checks with Lighthouse or other tools to catch accessibility or performance regressions. After testing, deploy to a staging branch and validate the final site before merging to production.

Checklist before production
  • All color changes centralized in variables
  • Fonts loaded with font-display swap
  • Images optimized and lazy loaded
  • Contrast and semantic HTML checked
  • Backup of original theme files committed

Common problems and fixes

When customizing you may encounter a few recurring issues. Below are typical problems and fast fixes.

Problem 1 slow font loading

Solution use font-display swap preconnect to font host and consider self hosting critical weights only.

Problem 2 inconsistent spacing across components

Solution create a spacing scale using CSS variables such as --space-1 --space-2 and apply consistently. Avoid overwriting component margins deeply in CSS; prefer top level spacing utilities.

Problem 3 images overflow container

Solution ensure image rules include max-width: 100% and box sizing uses border-box.

Further resources

If you want ready snippets here are a few practical extras: an accessible skip link pattern small script snippet to toggle dark mode using CSS variables and a minified responsive grid you can drop in. Keep a single small stylesheet for overrides named theme-overrides.css and import it last so it naturally wins over base styles.

Dark mode snippet using variables
:root {
  --bg-page: #ffffff;
  --text-main: #222222;
}
[data-theme="dark"] {
  --bg-page: #0b1220;
  --text-main: #e6edf3;
}
html {
  background: var(--bg-page);
  color: var(--text-main);
}
  

FAQ

How do I find the right color contrast

Use online contrast checkers or browser extensions to test foreground and background pairs. Aim for at least 4.5 to 1 for body text and 3 to 1 for larger headings.

Can I change fonts without editing HTML templates

Yes if the theme includes a central stylesheet you can import fonts there or add an overrides stylesheet that includes the font link and font-family rules.

Will changing CSS hurt SEO

No CSS changes do not directly affect ranking but improved readability accessibility and mobile friendliness can indirectly help user experience signals which matter to search engines.

Is it better to use system fonts for performance

System fonts are fastest since no network request is required. If performance is a priority choose a system stack and reserve web fonts for brand critical headings.

Final note choose one or two high impact changes per release. Tweak colors or fonts first then iterate on layout. Keep a backup and test each change in staging. If you want I can create a ready to paste override stylesheet and a short list of the exact selectors to update for the specific Mediumish clone you use.

How can I boost SEO for my Mediumish blog with metadata

Table of contents

Why metadata matters for SEO

Metadata is the set of descriptive tags and structured content that sit in the head of your HTML or as machine-readable blocks. For search engines and social platforms, metadata is often the first (and sometimes only) context they get about a page. That means good metadata helps search engines understand your content, pick the right snippet for search results, and create attractive link previews for social sharing.

For a Mediumish-style blog — usually lightweight, content-first, and minimal in layout — metadata is the easiest leverage point to improve visibility without redesigning the whole site. Proper metadata clarifies topic, author, and intended audience to Google and other services. It also increases click-through rate (CTR) by influencing the title and description users see in search results and on social feeds.

Think of metadata as an elevator pitch for the page: search engines parse it to decide where to place the page in results, and users scan it before clicking. A well-crafted title tag and description can make the difference between a visit and a pass. Structured data then provides a second layer: it tells engines exactly what kind of content you have (article, how-to, FAQ), enabling enhanced features like rich results and knowledge panels.

What metadata to use for a Mediumish blog

Not all metadata is equal. There are a few core pieces you should add and maintain for every post on your Mediumish blog. Below we explain each, why it matters, and how to implement it in a clean, minimal way that fits a Mediumish theme.

HTML meta tags (title, description)

Title and meta description are the most visible metadata elements. The title tag defines the headline Google shows (unless it rewrites it). The meta description is the short summary that appears under the title in search results. Together they influence ranking indirectly (through CTR) and provide primary signals about topic relevance.

Keep titles concise and descriptive, ideally 50–60 characters so they are not truncated in results. Use the meta description to summarize the article intent in 120–155 characters. Both should include the main keyword naturally, but avoid keyword stuffing — clarity and usefulness matter more than exact matches.

For Mediumish blogs where templates are small, generate title and description per post. If your platform supports front matter or post metadata, place these values there and render them into the head of your HTML using template variables.

Open Graph and Twitter cards

Open Graph (OG) tags and Twitter card tags are the metadata used by social networks to generate link previews. When someone shares an article, social platforms look for these tags to build a card with title, description, and image. Without them, platforms guess — and guesses are often ugly or misleading.

Use OG tags to control the image, title, and description shown on platforms such as Facebook, LinkedIn, and others. Twitter has its own card types but also honors OG tags as a fallback. For maximum control include both OG and Twitter tags.

Choose a clear, legible preview image sized to the recommended dimensions (e.g., 1200 × 630 px for Open Graph). For Mediumish blogs that emphasize text, a simple branded image with the article title and author photo works very well.

Structured data and JSON-LD

Structured data is machine-readable markup that explains the content's type and properties. Google recommends JSON-LD for structured data because it is easy to add and maintain. Depending on the content, you can use schema types like Article, BlogPosting, HowTo, FAQPage, or BreadcrumbList to unlock rich result features.

For Mediumish posts, the common useful schema types are BlogPosting (for the article), BreadcrumbList (to show location), and FAQPage (if you have frequently asked questions). Adding structured data does not guarantee rich results, but it increases the chance and helps search engines confidently understand your content structure.

Keep structured data accurate and consistent with visible content. If you declare an author name or publish date in JSON-LD, those values should match what users see. Google may remove rich features if there are mismatches or spammy claims.

Practical examples and templates

Below are ready-to-use examples you can copy into your Mediumish post template. Each example is minimal and crafted to match a lightweight blog head section. Replace placeholder text with actual values from your post.

Basic head with title and meta description

<meta charset="utf-8">
<title>How to write evergreen blog posts for beginners</title>
<meta name="description" content="Short summary of the article that entices clicks and explains the main benefit in 120 to 155 characters.">

Place these inside the <head> of your template (or the platform head injection area). Keep encoding and viewport tags too if your template omits them.

Open Graph and Twitter card example

<meta property="og:title" content="How to write evergreen blog posts for beginners">
<meta property="og:description" content="Step-by-step tips for creating evergreen posts that attract search traffic and backlinks.">
<meta property="og:type" content="article">
<meta property="og:url" content="https://example.com/article-slug">
<meta property="og:image" content="https://example.com/images/article-og.jpg">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="How to write evergreen blog posts for beginners">
<meta name="twitter:description" content="Step-by-step tips for creating evergreen posts that attract search traffic and backlinks.">
<meta name="twitter:image" content="https://example.com/images/article-og.jpg">

Use absolute URLs for images and canonical links. The image should be accessible by crawlers (no blocked paths).

JSON-LD BlogPosting example

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "How to write evergreen blog posts for beginners",
  "description": "Step-by-step tips for creating evergreen posts that attract search traffic and backlinks.",
  "url": "https://example.com/article-slug",
  "image": "https://example.com/images/article-og.jpg",
  "author": {
    "@type": "Person",
    "name": "Jane Author"
  },
  "publisher": {
    "@type": "Organization",
    "name": "Example Blog",
    "logo": {
      "@type": "ImageObject",
      "url": "https://example.com/images/logo.png"
    }
  },
  "datePublished": "2025-01-15",
  "dateModified": "2025-01-16"
}
</script>

Add BreadcrumbList JSON-LD if your template uses categories or hierarchical URLs. This helps Google render breadcrumb links in search results.

FAQ JSON-LD (for bots)

Below is a compact FAQ JSON-LD block. Place this in the head or at the end of the body. It is intended for consumption by search engines and should reflect the actual FAQ content visible on the page.

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [
    {
      "@type": "Question",
      "name": "What meta tags matter most for SEO?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Title tag and meta description are the most visible; structured data can help unlock rich results."
      }
    },
    {
      "@type": "Question",
      "name": "Do I need Open Graph tags for every post?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Yes, Open Graph tags are recommended to control social previews and improve CTR on social platforms."
      }
    }
  ]
}
</script>

Ensure your visible FAQ matches the questions in JSON-LD. Mismatches can lead to manual actions or ignored markup.

Small table: recommended image sizes

Platform Recommended image size Notes
Open Graph (Facebook, LinkedIn) 1200 × 630 px Use wide image, high contrast text if adding overlay
Twitter Large Card 1200 × 628 px Similar to OG; crop may differ on some devices
Article header (site) 1200 × 800 px Higher height works on article pages; OG will still use the main image

Common mistakes to avoid

Even small mistakes can reduce the effectiveness of metadata. One common error is using duplicate title tags across posts. Each post should have a unique, descriptive title. Duplicate titles make it harder for search engines to determine which page to show for queries and can lead to lower CTR.

Another frequent slip is neglecting the preview image or using an image that’s blocked by robots.txt. Social platforms will either pick a random image or show nothing, which looks unprofessional. Always use absolute image URLs and verify they are accessible.

Overusing schema types or adding irrelevant structured data is also risky. Only mark up content that exists and is visible to users. For example, don’t mark up a review if there is no genuine review content on the page.

Measuring impact and testing

After implementing metadata and structured data, measure results. Use Google Search Console to monitor impressions, clicks, and average position. Check the Performance report before and after changes to see CTR and ranking changes for targeted queries.

For social previews, use tools like the Facebook Sharing Debugger and Twitter Card Validator to preview how your page will appear when shared. These tools also force a cache refresh so you can test corrections immediately.

Run an A/B test for title and description if your platform supports it, or rotate variants manually over several weeks. Small improvements in CTR compound over time and can significantly boost organic traffic for evergreen posts.

FAQ

How often should I update metadata?

Update metadata whenever you significantly change content, target audience, or keywords. For evergreen posts a quarterly review is a good cadence: check if the title, description, and image still match intent and whether structured data remains accurate.

Will JSON-LD guarantee rich results?

No. JSON-LD does not guarantee rich results. It only increases the chance because it helps search engines understand the content. Google uses many signals to decide whether to render rich features.

Where should I host OG images for best performance?

Host images on the same domain as your site or on a fast CDN. This reduces latency and avoids cross-domain issues. Also make sure images are optimized (webp or compressed jpeg) and served with caching headers.

This guide focused on metadata, Open Graph, and structured data tailored for a Mediumish style blog. Implement these cautiously and test results using Search Console and social debuggers. Proper metadata is one of the highest-ROI edits you can make to a lightweight content-focused site — it improves clarity for machines and appeal for humans, and that combination directly helps search performance over time.

How do I migrate content to the Mediumish theme

Table of contents

Why migrate to Mediumish

Choosing the Mediumish theme is often about clean typography, reader focus, and a layout that favors long-form content. If your goal is to present posts that are easy to read on mobile and desktop, and you want a minimalist aesthetic with modern UI patterns, Mediumish fits well. Migrating content is not only a visual change — it is a chance to improve structure, clean up legacy SEO problems, and standardize metadata for future growth.

While a migration can be technical, it is primarily a content exercise: you move words, images, and links, and then verify that search engines and readers still find your articles valuable. A good migration keeps or improves search visibility, preserves social shares where possible, and avoids broken links. Throughout the process you should plan to back up everything and work incrementally.

Prepare and audit your existing content

Start with a complete inventory. Export a list of all published URLs, titles, publish dates, categories, tags, and authors. Many platforms let you export CSV or XML sitemaps. If a direct export is not available, crawl your site with a crawler to produce a URL list. This inventory becomes your migration map and ensures you don't forget pages, images, or attachments.

Next, assess content quality and relevance. Migration is the perfect time to prune outdated posts, consolidate duplicate articles, and improve thin pages. For each URL decide: migrate as-is, update before migrating, merge with another page, or retire with a redirect. Reducing the number of low-value pages will make the new site cleaner and improve crawl efficiency.

Finally, note SEO-critical elements: meta titles, meta descriptions, canonical tags, structured data, and high-value internal links. Record any posts that receive regular traffic or backlinks — these need careful permalink handling so you don't lose ranking or referral traffic.

Exporting from common platforms

WordPress

WordPress gives the most straightforward export options. Use the native Tools → Export to generate an XML file containing posts, pages, comments, categories, and authors. Many migrations use the WordPress WXR (XML) file to import content into another system or to convert posts to Markdown or HTML for Mediumish.

If your Mediumish setup uses Jekyll, Hugo, or a static generator, consider plugins or tools that convert WXR to Markdown with front matter. Tools like wxr2md, exitwp, or custom scripts can transform the XML into a series of Markdown files with YAML front matter that the Mediumish layout can read.

Blogger

Blogger can export an XML file from Settings → Other → Back up content. The Blogger XML includes posts and comments. Converting this XML to Markdown or HTML files often requires a parser. Simple scripts or online converters can help, but pay attention to inline styles and embedded widgets — these may not translate well and often need manual cleanup.

Images hosted on Blogger typically live on Google's servers with long URLs. Decide whether to keep serving images from those URLs or download and rehost them under your new domain (recommended for long-term stability and analytics).

Ghost

Ghost has a JSON export that includes posts, tags, authors, and images (image URLs point to Ghost storage). Ghost’s JSON structure is modern and rich, but you will likely need a conversion step to produce Markdown or HTML files with appropriate front matter fields. Ghost images can be bulk-downloaded or preserved via their existing URLs if they are stable.

Medium

Medium allows you to export posts as a ZIP containing HTML files and images. The HTML is clean but may include Medium-specific markup for embeds or references. Each exported post is an HTML file you can adapt to the Mediumish structure. Medium does not provide an easy way to export comments or all metadata (like claps) — preserve what matters for SEO and content continuity (title, body, publish date, canonical link).

Custom or static sites

If your site is already static, migration is often the simplest: copy the Markdown or HTML files and adjust front matter to the Mediumish schema. If pages were hand-crafted with unique classes, you will need to standardize markup so Mediumish styling works properly. Keep an eye on inline CSS and custom scripts that may conflict with the theme.

Map your structure and front matter

Mediumish themes expect a consistent set of metadata so templates can render author, date, tags, cover image, and reading time. Create a front matter template and decide field names. A typical front matter block might include:

---
title: "Your post title"
date: "2024-04-01"
author: "Author Name"
tags: ["tag1", "tag2"]
categories: ["category"]
cover: "/assets/images/cover.jpg"
excerpt: "Short summary for meta description"
canonical: "https://oldsite.com/original-url"
---

When converting exported content, populate these fields for every post. If your export lacks an excerpt or canonical URL, generate a sensible excerpt and set canonical where necessary to avoid duplicate content penalties.

Also plan URL structure and permalink templates. Decide whether Mediumish will adopt your old permalink pattern or use a new one. Keeping the same pattern reduces redirect complexity, but changing can be OK if you implement robust redirects.

Images and media handling

Images are one of the most commonly overlooked migration details. Options for each image include:

  1. Keep the original host URL (fast, minimal work, but external dependency).
  2. Download all images and rehost under your new domain (recommended for control and future-proofing).
  3. Use a CDN or cloud storage bucket and serve images from there.

If you choose to rehost, download images at their highest reasonable resolution and create a consistent path (for example, /assets/images/posts/yyyy/mm/slug-image.jpg). Update post references so all <img> tags point to the new paths. If you use a static generator, add an images folder per post or a single assets folder depending on your organization preference.

Don’t forget to preserve alt attributes for accessibility and SEO. If alt text is missing, create brief descriptive alt text during the migration. Also consider adding srcset attributes or using responsive image helpers that Mediumish might support to optimize loading on different devices.

Preserving link equity is critical. Before you switch DNS or publish the new site, prepare a redirect plan. Common redirect strategies:

  • Server-level redirects (Apache .htaccess or Nginx rules) — ideal if you control the original host.
  • Hosting provider redirects — many managed hosts offer redirect rules or UI for mapping old to new URLs.
  • Static redirect files — for static hosts you can create a set of HTML files at old paths with meta refresh or JavaScript redirects, though server redirects are preferable for SEO.

Export your old URL list and map it to new URLs. Keep the redirects in place at least 6–12 months, and monitor traffic to catch any residual broken links. For large sites, consider automating redirect generation using scripts that match slugs and dates.

SEO, metadata and schema

Mediumish pages should include clear meta titles and descriptions. During migration, ensure each page has a unique meta title (60–70 characters best practice) and meta description (up to 150 characters to match your request). If you create new or improved excerpts, add them to the front matter and use them as meta descriptions.

Structured data matters. Add schema for articles and FAQs where relevant. Below is an example JSON-LD for an article. Insert it into the template so each post generates accurate structured data with headline, author, datePublished, and image fields:

{
  "@context": "https://schema.org",
  "@type": "Article",
  "headline": "Example headline",
  "author": {
    "@type": "Person",
    "name": "Author Name"
  },
  "datePublished": "2024-04-01",
  "image": [
    "https://example.com/assets/images/cover.jpg"
  ],
  "publisher": {
    "@type": "Organization",
    "name": "Your Site",
    "logo": {
      "@type": "ImageObject",
      "url": "https://example.com/assets/images/logo.png"
    }
  }
}

Include canonical links that point to the primary version of the content. If you migrate and change the canonical domain, ensure the canonical points to the new domain after the migration, or temporarily point to the old canonical during content transition if you are preserving old pages while testing.

Comments and analytics

Comments may live on third-party systems (Disqus, native, or platform-specific). For Disqus, export comment threads if you want to import them into a new Disqus shortname or link them to new URLs. Disqus supports migration guides that map old URLs to new posts. For native comments in WordPress or other platforms, use export tools to preserve comment metadata.

Analytics tracking must be added to the new theme. If you used Google Analytics or another provider, place the tracking snippet in Mediumish’s template or through your site’s tag manager. Verify tracking after launch by checking real-time reports. Consider setting up Search Console and submitting a new sitemap after migration to speed re-indexing.

Testing and go-live checklist

Before switching DNS or making the site public, test the new theme in a staging environment. Key tests include:

  • Rendering: ensure headings, images, code blocks and blockquotes look correct across posts.
  • Permalink matching: verify the mapping of old URLs to new ones and test redirects.
  • Mobile responsiveness: check pages on several device widths.
  • Schema and meta tags: validate with Rich Results Test or schema validators.
  • Performance: measure page load times and optimize images and CSS where needed.

When you are ready, switch over during a low-traffic time if possible, and monitor server logs, Search Console, and analytics closely for 72 hours. Expect search engines to re-crawl — keep redirects active and be patient; ranking changes may fluctuate briefly.

Common issues and solutions

Broken images: If images break after migration, check file paths and case-sensitivity. On some hosts, filenames are case-sensitive; a mismatch in capitalization will cause errors. Fix by correcting the URLs or re-uploading images with normalized names.

Missing metadata: Old exports sometimes lack meta descriptions or canonical tags. Fix by updating front matter in bulk (scripts or find-and-replace) to include generated excerpts or canonical values.

Loss of social previews: If social cards show broken or wrong images, verify Open Graph metadata and ensure your social images meet recommended sizes. Clear caches for Facebook or Twitter Card validator to refresh previews.

Redirect loops: Misconfigured redirects can create loops. Test a few URLs manually and use online redirect checkers. Keep a clean one-to-one mapping where possible and avoid redirecting old URLs to pages that then redirect again.

Quick migration checklist

Step Description
Inventory Export URL list, titles, dates, and SEO metadata
Decide Keep, update, merge, or retire each post
Export Run platform export (XML, JSON, ZIP) and collect media
Convert Transform to HTML/Markdown with Mediumish front matter
Images Download, optimize and rehost images; update references
Redirects Prepare server-level or hosting redirects from old to new URLs
Test Validate rendering, SEO, analytics, and mobile
Go live Push DNS or publish and monitor traffic and errors

FAQ

How long will migration take

Migration time depends on site size, media volume, and custom features. Small blogs (under 200 posts) can often be migrated within a few days with careful testing. Larger sites may take weeks. Plan for extra time to fix redirects and SEO issues.

Will I lose SEO rankings after switching to Mediumish

If you keep permalinks or implement correct redirects and preserve metadata and structured data, you can avoid major ranking losses. Expect short-term fluctuations as search engines re-crawl; monitor Search Console and adjust as needed.

Can I import comments and social shares

Comments can often be exported and reattached (Disqus has import tools; WordPress comments can be exported). Share counts are harder—social platforms usually use URL-based counts, so if URLs change you may lose counts unless you use tools that aggregate counts for old and new URLs.

Should I change URLs to a cleaner structure

Changing to a cleaner URL format is tempting, but only do so if you can implement reliable redirects. The benefits to UX and long-term maintainability can outweigh the short-term effort, but always map old to new and keep redirects live for months.

Follow this guide step by step and treat the migration as both a technical and editorial project. Mediumish gives you a clean canvas — use the opportunity to tidy content, strengthen metadata, and improve the reading experience. If you want, I can generate a custom migration script for a specific export file (WordPress XML, Blogger XML, Ghost JSON, or Medium ZIP) and a matching front matter template for Mediumish. Tell me which platform you are migrating from and share a sample export file or a short list of URLs, and I will prepare an actionable conversion plan.

Want a dark mode switch for Mediumish

Why add a dark mode switch?

Dark mode has moved beyond a trendy visual tweak — it improves legibility in low-light environments, reduces perceived glare on OLED/mobile displays, and can make reading long articles more comfortable at night. For a content-focused theme like Mediumish, a simple dark mode switch boosts user experience and can reduce bounce rates by letting readers choose the look that fits their environment.

Adding a dark mode switch also signals to users that your site is modern and accessible. It is an evergreen UX enhancement: once implemented correctly, it keeps working for many years with minimal maintenance.

How dark mode works (simple)

At a high level dark mode is just another set of visual rules (colors, shadows, borders) applied to the site. The usual pattern is:

  1. Define CSS variables for colors and other visual tokens.
  2. Provide a light theme (default) and a dark theme that overrides the variables.
  3. Use a toggle (HTML + JavaScript) to switch themes and persist the choice in localStorage or a cookie.
  4. Optionally respect the user's system preference using prefers-color-scheme.

This keeps the HTML structure unchanged — only the CSS variables change. That approach is robust and easy to maintain.

Design and accessibility considerations

Before you implement, consider these important factors so dark mode is comfortable and usable:

  • Contrast: Ensure text meets WCAG minimum contrast ratios against the background. For body text aim for a contrast ratio of at least 4.5:1. Use softer grays rather than pure white on black to avoid eye strain.
  • Semantic markup: Keep semantics intact (headings, lists, code blocks) so assistive tech is unaffected.
  • Focusable toggle: Make the switch keyboard accessible, labelled for screen readers, and visible when focused.
  • Persist preference: Save the user choice so returning visitors see their chosen theme immediately.
  • System preference: Respect prefers-color-scheme by default, but allow the user to override it.
  • Images and media: Some images with transparent backgrounds or light logos may need alternate versions or CSS filters in dark mode.
  • Syntax highlighting: If your theme features code blocks, provide a dark-friendly syntax theme to keep code readable and pleasant.

CSS approach: variables and theme rules

Use CSS custom properties (variables) at :root and an attribute on html (for example data-theme="dark") to switch values. This minimizes the number of selectors you need to change.

Example CSS to add to your main stylesheet (put near the top so it applies everywhere):

/* Theme variables: default (light) */
:root{
  --bg: #ffffff;
  --surface: #f7f7f7;
  --text: #111827;
  --muted: #6b7280;
  --link: #1a73e8;
  --border: #e5e7eb;
  --code-bg: #f3f4f6;
  --shadow: 0 1px 2px rgba(16,24,40,0.04);
  --accent: #111827;
}

/* Dark theme overrides when data-theme="dark" */
:root[data-theme="dark"]{
  --bg: #0b0f12;
  --surface: #0f1720;
  --text: #e6eef8;
  --muted: #9aa7b5;
  --link: #66a6ff;
  --border: #1f2933;
  --code-bg: #071126;
  --shadow: none;
  --accent: #e6eef8;
}

/* Respect system preference if user has not set anything yet */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]){
    --bg: #0b0f12;
    --surface: #0f1720;
    --text: #e6eef8;
    --muted: #9aa7b5;
    --link: #66a6ff;
    --border: #1f2933;
    --code-bg: #071126;
  }
}

/* Apply tokens to theme elements */
html, body{
  background: var(--bg);
  color: var(--text);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;
  line-height: 1.7;
}

/* Content card and post surface */
.post, .content, .container{
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow);
  color: var(--text);
}

/* Links */
a{
  color: var(--link);
}

/* Code blocks */
pre, code{
  background: var(--code-bg);
  color: var(--text);
  border-radius: 6px;
  padding: 0.2rem 0.4rem;
}

/* Invert SVG strokes if needed */
img.invert-on-dark{
  filter: none;
}
:root[data-theme="dark"] img.invert-on-dark{
  filter: invert(1) hue-rotate(180deg) brightness(1.1);
}

Notes:

  • Use var(--...) everywhere instead of hard-coded colors in other rules. Replace existing hard-coded color values in Mediumish with variables.
  • Place the @media (prefers-color-scheme: dark) block so users with no saved preference get a sensible default.

HTML markup: toggle button

Add a small, accessible toggle where your site header or utility area is rendered. Because you asked not to use <header> element, insert the button in your theme’s top right area (for example inside the theme’s existing site controls). The markup below is simple and screen-reader friendly.

<div class="theme-toggle-wrapper">
  <button id="theme-toggle" aria-label="Toggle dark mode" aria-pressed="false">
    <span class="sr-only">Toggle dark mode</span>
    <svg class="icon-sun" width="18" height="18" viewBox="0 0 24 24" aria-hidden="true">...sun SVG path...</svg>
    <svg class="icon-moon" width="18" height="18" viewBox="0 0 24 24" aria-hidden="true" hidden>...moon SVG path...</svg>
  </button>
</div>

CSS for the toggle (simple inline styles you can adapt):

.theme-toggle-wrapper{ display:inline-block; margin-left:12px; }
#theme-toggle{
  background: transparent;
  border: 1px solid var(--border);
  padding:6px;
  border-radius:8px;
  display:inline-flex;
  align-items:center;
  justify-content:center;
  cursor:pointer;
}
#theme-toggle:focus{ outline: 2px solid var(--link); outline-offset: 2px; }
.icon-moon[hidden], .icon-sun[hidden]{ display:none; }

Notes:

  • Use SVG icons for crisp visuals. Only one icon should be visible at a time.
  • Set aria-pressed to reflect the toggle state for assistive tech.

JavaScript: saving preference and toggling

The JS below toggles the data-theme attribute on the document element, updates the button state, and saves the preference to localStorage. Put this script before the closing <body> tag or as an inline script in your theme so it runs early enough to avoid a flash of the wrong theme.

(function(){
  const storageKey = 'site-theme-preference';
  const themeAttr = 'data-theme';
  const toggle = document.getElementById('theme-toggle');

  function applyTheme(theme){
    if(theme){
      document.documentElement.setAttribute(themeAttr, theme);
    } else {
      document.documentElement.removeAttribute(themeAttr);
    }
  }

  function getSavedTheme(){
    try{
      return localStorage.getItem(storageKey);
    }catch(e){
      return null;
    }
  }

  function saveTheme(theme){
    try{
      if(theme) localStorage.setItem(storageKey, theme);
      else localStorage.removeItem(storageKey);
    }catch(e){}
  }

  function userPrefersDark(){
    return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
  }

  // Init
  const saved = getSavedTheme();
  if(saved === 'dark'){
    applyTheme('dark');
    if(toggle) toggle.setAttribute('aria-pressed','true');
  } else if(saved === 'light'){
    applyTheme('light');
    if(toggle) toggle.setAttribute('aria-pressed','false');
  } else if(userPrefersDark()){
    // Respect system default only when no saved preference
    applyTheme('dark');
    if(toggle) toggle.setAttribute('aria-pressed','true');
  }

  // Avoid flash: hide body until theme applied (optional progressive enhancement)
  // document.documentElement.style.visibility = 'visible';

  if(toggle){
    toggle.addEventListener('click', function(){
      const isPressed = toggle.getAttribute('aria-pressed') === 'true';
      const next = isPressed ? 'light' : 'dark';
      applyTheme(next === 'light' ? 'light' : 'dark');
      saveTheme(next === 'light' ? 'light' : 'dark');
      toggle.setAttribute('aria-pressed', String(!isPressed));
      // switch visible icon
      const sun = toggle.querySelector('.icon-sun');
      const moon = toggle.querySelector('.icon-moon');
      if(sun && moon){
        if(next === 'dark'){ sun.setAttribute('hidden',''); moon.removeAttribute('hidden'); }
        else { moon.setAttribute('hidden',''); sun.removeAttribute('hidden');}
      }
    });
  }
})();

Notes and improvements:

  • You can choose to remove data-theme="light" when in light mode and only set data-theme="dark" for dark, keeping markup smaller.
  • If you want instant application before JS runs, render a small inline script in the document head that reads localStorage and sets the attribute synchronously to prevent a flash of theme (FOIT). Keep it tiny and safe.
  • Be mindful of environments where localStorage is not available (private browsing on some browsers). The try/catch guards help.

Integrating with the Mediumish theme

Mediumish typically uses a clean content container with classes like .post, .page, and .site. The safest integration approach:

  1. Find the theme's main stylesheet and replace color constants with CSS variables (search for hex values and substitute with var(--...) where appropriate).
  2. Add the variables block (the :root and :root[data-theme="dark"]) at the top of the stylesheet or in a small new stylesheet loaded before the theme stylesheet.
  3. Insert the toggle button markup in the theme's site controls (top-right area). If Mediumish has a partial/template for site controls, edit that file so the toggle appears site-wide.
  4. Add the JavaScript snippet before the closing body tag, or inline in the theme’s head (the head inline snippet is recommended for preventing flash of wrong theme).
  5. Test all pages: homepage, single post, tag pages, archives, pagination, and mobile menus.

Example of a minimal inline head script that sets the attribute early (place it inside the <head>, as the very first inline script):

try{
  const k='site-theme-preference';
  const s=localStorage.getItem(k);
  if(s==='dark'){ document.documentElement.setAttribute('data-theme','dark'); }
  else if(s==='light'){ document.documentElement.removeAttribute('data-theme'); }
  // if no saved preference, we let CSS @media handle the system preference
}catch(e){};

Warning: If the theme bundles CSS with hard-coded colors in many places, you must audit and replace those colors with variables, otherwise the dark theme will be inconsistent.

Handling images and code syntax highlighting

Images: icons or logos with transparent backgrounds may look wrong on dark backgrounds. Options:

  • Use alternate darker or lighter versions of the image and show them via CSS when data-theme="dark".
  • Use the CSS filter trick for simple monochrome icons (see the img.invert-on-dark rule earlier).
  • For complex illustrations, provide two files and switch the src via CSS background-image or swap with small JavaScript when theme changes.

Code syntax: If your site features code blocks, adopt or include a dark theme for your syntax highlighter (Prism, Highlight.js, etc.). Example: add a dark version CSS and scope it to data-theme="dark".

/* Example: Prism light baseline */
pre[class*="language-"] { background: var(--code-bg); color: var(--text); }

/* Dark overrides */
:root[data-theme="dark"] pre[class*="language-"] {
  background: #071126;
  color: #e6eef8;
}
/* Load a dark prism theme file or provide token colors here */

Testing, troubleshooting, and deployment

Thorough testing steps:

  1. Test on mobile and desktop. Check pages with images, galleries, code blocks, tables, and forms.
  2. Verify keyboard focus states for the toggle and other interactive elements.
  3. Open the site in an incognito window to ensure localStorage behavior is graceful.
  4. Test with different system preferences (macOS and mobile) to see how the site responds when no user preference is saved.
  5. Check color contrast ratios with a tool (many browser extensions or automated tests) to ensure readability.

Common issues and fixes:

  • Flash of wrong theme: Use a tiny inline head script to set data-theme from localStorage before stylesheets render.
  • Hard-coded colors still appear: Replace any remaining hard-coded colors in the theme CSS with variables, or add specific overrides under :root[data-theme="dark"] for the problematic selectors.
  • Images look washed out: Provide alternate assets or use filter sparingly; filters can degrade image quality.
  • Icons not switching: Confirm your toggle updates the visible icon and uses hidden attribute or toggles a class—SVG with contrast-aware fills helps.

FAQ

Q: Will dark mode hurt SEO or page speed?

A: No. Properly implemented dark mode uses CSS and a few bytes of JavaScript. It does not affect indexability. Avoid inlining large assets just for theme switching; keep the inline script minimal to prevent perceived flashes.

Q: Should I store the preference server-side?

A: For logged-in users you can store the preference in the user profile, which is handy across devices. For anonymous visitors, client-side storage (localStorage) is simple and effective.

Q: How do I test color contrast?

A: Use browser extensions like Accessibility Insights or Lighthouse audits. Check body text, headings, links, and interactive elements to ensure contrast ratios meet WCAG guidelines.

Q: Can I add an "auto" option that follows system preference?

A: Yes. Provide three states: light, dark, and auto. Store the user's explicit choice. If the user picks "auto", remove the saved entry and let prefers-color-scheme handle the default.

Q: Is there a recommended palette?

A: Use neutral backgrounds (not pure black) and off-white text to reduce strain. Blues and accent hues should remain readable in both modes. Test combined elements (links on surface, buttons on cards).

Practical example: Minimal patch checklist

  • 1. Add CSS variables block to top of theme stylesheet.
  • 2. Replace color tokens in core selectors with var(--...).
  • 3. Add toggle HTML to the theme's global controls.
  • 4. Add JS to read/save preference and toggle data-theme.
  • 5. Add dark versions for charts, images, and code blocks if necessary.
  • 6. Test across pages and fix any remaining hard-coded color issues.

Short checklist for production

ActionCompleted
Variables added
Toggle UI added
Persist preference
Images handled
Code style updated
Accessibility check

Follow this pattern and your Mediumish-based site will have a reliable, accessible, and persistent dark mode that improves user comfort and modernizes your reading experience. The approach scales: once variables are in place, future visual changes are trivial.