diff --git a/ACKNOWLEDGEMENTS.md b/ACKNOWLEDGEMENTS.md
index 27f6686e..f8bdec5d 100644
--- a/ACKNOWLEDGEMENTS.md
+++ b/ACKNOWLEDGEMENTS.md
@@ -35,6 +35,7 @@ Supercell Wx uses code from the following dependencies:
 | [nunicode](https://bitbucket.org/alekseyt/nunicode/src/master/) | [MIT License](https://spdx.org/licenses/MIT.html) | Modified for MapLibre Native |
 | [OpenSSL](https://www.openssl.org/) | [OpenSSL License](https://spdx.org/licenses/OpenSSL.html) |
 | [Qt](https://www.qt.io/) | [GNU Lesser General Public License v3.0 only](https://spdx.org/licenses/LGPL-3.0-only.html) | Qt Core, Qt GUI, Qt Multimedia, Qt Network, Qt OpenGL, Qt Positioning, Qt SQL, Qt SVG, Qt Widgets
Additional Licenses: https://doc.qt.io/qt-6/licenses-used-in-qt.html |
+| [re2](https://github.com/google/re2) | [BSD 3-Clause "New" or "Revised" License](https://spdx.org/licenses/BSD-3-Clause.html) |
 | [spdlog](https://github.com/gabime/spdlog) | [MIT License](https://spdx.org/licenses/MIT.html) |
 | [SQLite](https://www.sqlite.org/) | Public Domain |
 | [stb](https://github.com/nothings/stb) | Public Domain |
diff --git a/conanfile.py b/conanfile.py
index 9f100e2e..d03504b7 100644
--- a/conanfile.py
+++ b/conanfile.py
@@ -13,6 +13,7 @@ class SupercellWxConan(ConanFile):
                   "libcurl/8.4.0",
                   "libxml2/2.12.2",
                   "openssl/3.2.0",
+                  "re2/20231101",
                   "spdlog/1.12.0",
                   "sqlite3/3.44.2",
                   "vulkan-loader/1.3.243.0",
diff --git a/scwx-qt/source/scwx/qt/manager/update_manager.cpp b/scwx-qt/source/scwx/qt/manager/update_manager.cpp
index 213354ec..08304263 100644
--- a/scwx-qt/source/scwx/qt/manager/update_manager.cpp
+++ b/scwx-qt/source/scwx/qt/manager/update_manager.cpp
@@ -2,10 +2,10 @@
 #include 
 
 #include 
-#include 
 
 #include 
 #include 
+#include 
 
 namespace scwx
 {
@@ -61,16 +61,10 @@ std::string UpdateManager::latest_version() const
 std::string
 UpdateManager::Impl::GetVersionString(const std::string& releaseName)
 {
-   static const std::regex re {"\\d+\\.\\d+\\.\\d+"};
-   std::string             versionString {};
-   std::smatch             m;
+   static constexpr LazyRE2 re = {"(\\d+\\.\\d+\\.\\d+)"};
+   std::string              versionString {};
 
-   std::regex_search(releaseName, m, re);
-
-   if (!m.empty())
-   {
-      versionString = m[0].str();
-   }
+   RE2::PartialMatch(releaseName, *re, &versionString);
 
    return versionString;
 }
diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp
index a897748b..ebef32e7 100644
--- a/scwx-qt/source/scwx/qt/map/map_widget.cpp
+++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp
@@ -23,7 +23,6 @@
 #include 
 #include 
 
-#include 
 #include 
 
 #include 
@@ -33,6 +32,7 @@
 #include 
 #include 
 #include 
+#include 
 #include 
 #include 
 #include 
@@ -769,14 +769,14 @@ std::string MapWidgetImpl::FindMapSymbologyLayer()
       const std::string layer = qlayer.toStdString();
 
       // Draw below layers defined in map style
-      auto it = std::find_if(
-         currentStyle_->drawBelow_.cbegin(),
-         currentStyle_->drawBelow_.cend(),
-         [&layer](const std::string& styleLayer) -> bool
-         {
-            std::regex re {styleLayer, std::regex_constants::icase};
-            return std::regex_match(layer, re);
-         });
+      auto it = std::find_if(currentStyle_->drawBelow_.cbegin(),
+                             currentStyle_->drawBelow_.cend(),
+                             [&layer](const std::string& styleLayer) -> bool
+                             {
+                                // Perform case-insensitive matching
+                                RE2 re {"(?i)" + styleLayer};
+                                return RE2::FullMatch(layer, re);
+                             });
 
       if (it != currentStyle_->drawBelow_.cend())
       {
diff --git a/scwx-qt/source/scwx/qt/settings/palette_settings.cpp b/scwx-qt/source/scwx/qt/settings/palette_settings.cpp
index 8133a452..dcf58f2e 100644
--- a/scwx-qt/source/scwx/qt/settings/palette_settings.cpp
+++ b/scwx-qt/source/scwx/qt/settings/palette_settings.cpp
@@ -2,10 +2,9 @@
 #include 
 #include 
 
-#include 
-
 #include 
 #include 
+#include 
 
 namespace scwx
 {
@@ -134,8 +133,8 @@ public:
 
 bool PaletteSettings::Impl::ValidateColor(const std::string& value)
 {
-   static const std::regex re {"#[0-9A-Za-z]{8}"};
-   return std::regex_match(value, re);
+   static constexpr LazyRE2 re = {"#[0-9A-Fa-f]{8}"};
+   return RE2::FullMatch(value, *re);
 }
 
 PaletteSettings::PaletteSettings() :
diff --git a/test/source/scwx/qt/map/map_provider.test.cpp b/test/source/scwx/qt/map/map_provider.test.cpp
index f3883992..8b330de9 100644
--- a/test/source/scwx/qt/map/map_provider.test.cpp
+++ b/test/source/scwx/qt/map/map_provider.test.cpp
@@ -2,13 +2,12 @@
 #include 
 #include 
 
-#include 
-
 #include 
 #include 
 #include 
 
 #include 
+#include 
 
 namespace scwx
 {
@@ -108,14 +107,15 @@ TEST_P(ByMapProviderTest, MapProviderLayers)
             const std::string layer = qlayer.toStdString();
 
             // Draw below layers defined in map style
-            auto it = std::find_if(
-               mapStyle.drawBelow_.cbegin(),
-               mapStyle.drawBelow_.cend(),
-               [&layer](const std::string& styleLayer) -> bool
-               {
-                  std::regex re {styleLayer, std::regex_constants::icase};
-                  return std::regex_match(layer, re);
-               });
+            auto it =
+               std::find_if(mapStyle.drawBelow_.cbegin(),
+                            mapStyle.drawBelow_.cend(),
+                            [&layer](const std::string& styleLayer) -> bool
+                            {
+                               // Perform case insensitive matching
+                               RE2 re {"(?i)" + styleLayer};
+                               return RE2::FullMatch(layer, re);
+                            });
 
             if (it != mapStyle.drawBelow_.cend())
             {
diff --git a/wxdata/source/scwx/awips/text_product_message.cpp b/wxdata/source/scwx/awips/text_product_message.cpp
index 9b2a9149..ffbd41dc 100644
--- a/wxdata/source/scwx/awips/text_product_message.cpp
+++ b/wxdata/source/scwx/awips/text_product_message.cpp
@@ -4,11 +4,11 @@
 #include 
 
 #include 
-#include 
 #include 
 
 #include 
 #include 
+#include 
 
 namespace scwx
 {
@@ -24,7 +24,7 @@ static const auto        logger_    = scwx::util::Logger::Create(logPrefix_);
 // Segment Header only:
 // * _xM__day_mon__year_/_xM__day_mon__year/
 // Look for hhmm (xM|UTC) to key the date/time string
-static const std::regex reDateTimeString {"^[0-9]{3,4} ([AP]M|UTC)"};
+static constexpr LazyRE2 reDateTimeString = {"^[0-9]{3,4} ([AP]M|UTC)"};
 
 static void ParseCodedInformation(std::shared_ptr segment,
                                   const std::string&       wfo);
@@ -333,7 +333,7 @@ void ParseCodedInformation(std::shared_ptr segment,
       else if (codedMotionBegin != productContent.cend() &&
                codedMotionEnd == productContent.cend() &&
                !it->starts_with(" ") &&
-               !std::regex_search(*it, std::regex {"^[0-9]"})
+               !(it->length() > 0 && std::isdigit(it->at(0)))
                /* Continuation lines */)
       {
          codedMotionEnd = it;
@@ -448,7 +448,7 @@ std::vector TryParseMndHeader(std::istream& is)
    }
 
    if (!mndHeader.empty() &&
-       !std::regex_search(mndHeader.back(), reDateTimeString))
+       !RE2::PartialMatch(mndHeader.back(), *reDateTimeString))
    {
       // MND Header should end with an Issuance Date/Time Line
       mndHeader.clear();
@@ -489,8 +489,8 @@ std::optional TryParseSegmentHeader(std::istream& is)
    // UGC takes the form SSFNNN-NNN>NNN-SSFNNN-DDHHMM- (NWSI 10-1702)
    // Look for SSF(NNN)?[->] to key the UGC string
    // Look for DDHHMM- to end the UGC string
-   static const std::regex reUgcString {"^[A-Z]{2}[CZ]([0-9]{3})?[->]"};
-   static const std::regex reUgcExpiration {"[0-9]{6}-$"};
+   static constexpr LazyRE2 reUgcString     = {"^[A-Z]{2}[CZ]([0-9]{3})?[->]"};
+   static constexpr LazyRE2 reUgcExpiration = {"[0-9]{6}-$"};
 
    std::optional header = std::nullopt;
    std::string                  line;
@@ -498,14 +498,14 @@ std::optional TryParseSegmentHeader(std::istream& is)
 
    util::getline(is, line);
 
-   if (std::regex_search(line, reUgcString))
+   if (RE2::PartialMatch(line, *reUgcString))
    {
       header = SegmentHeader();
       header->ugcString_.push_back(line);
 
       // If UGC is multi-line, continue parsing
       while (!is.eof() && is.peek() != '\r' &&
-             !std::regex_search(line, reUgcExpiration))
+             !RE2::PartialMatch(line, *reUgcExpiration))
       {
          util::getline(is, line);
          header->ugcString_.push_back(line);
@@ -526,7 +526,7 @@ std::optional TryParseSegmentHeader(std::istream& is)
       while (!is.eof() && is.peek() != '\r')
       {
          util::getline(is, line);
-         if (!std::regex_search(line, reDateTimeString))
+         if (!RE2::PartialMatch(line, *reDateTimeString))
          {
             header->ugcNames_.push_back(line);
          }
@@ -553,12 +553,12 @@ std::optional TryParseVtecString(std::istream& is)
    // P-VTEC takes the form /k.aaa.cccc.pp.s.####.yymmddThhnnZB-yymmddThhnnZE/
    // (NWSI 10-1703)
    // Look for /k. to key the P-VTEC string
-   static const std::regex rePVtecString {"^/[OTEX]\\."};
+   static constexpr LazyRE2 rePVtecString = {"^/[OTEX]\\."};
 
    // H-VTEC takes the form
    // /nwsli.s.ic.yymmddThhnnZB.yymmddThhnnZC.yymmddThhnnZE.fr/ (NWSI 10-1703)
    // Look for /nwsli. to key the H-VTEC string
-   static const std::regex reHVtecString {"^/[A-Z0-9]{5}\\."};
+   static constexpr LazyRE2 reHVtecString = {"^/[A-Z0-9]{5}\\."};
 
    std::optional vtec = std::nullopt;
    std::string         line;
@@ -566,7 +566,7 @@ std::optional TryParseVtecString(std::istream& is)
 
    util::getline(is, line);
 
-   if (std::regex_search(line, rePVtecString))
+   if (RE2::PartialMatch(line, *rePVtecString))
    {
       bool vtecValid;
 
@@ -577,7 +577,7 @@ std::optional TryParseVtecString(std::istream& is)
 
       util::getline(is, line);
 
-      if (std::regex_search(line, reHVtecString))
+      if (RE2::PartialMatch(line, *reHVtecString))
       {
          vtec->hVtec_.swap(line);
       }
diff --git a/wxdata/source/scwx/awips/ugc.cpp b/wxdata/source/scwx/awips/ugc.cpp
index 124e13b4..efc6284f 100644
--- a/wxdata/source/scwx/awips/ugc.cpp
+++ b/wxdata/source/scwx/awips/ugc.cpp
@@ -2,12 +2,12 @@
 #include 
 
 #include