{"id":3565,"date":"2020-12-18T18:30:08","date_gmt":"2020-12-18T18:30:08","guid":{"rendered":"https:\/\/ionicframework.com\/blog\/?p=3565"},"modified":"2020-12-18T18:30:08","modified_gmt":"2020-12-18T18:30:08","slug":"best-practices-for-building-offline-apps","status":"publish","type":"post","link":"https:\/\/ionic.io\/blog\/best-practices-for-building-offline-apps","title":{"rendered":"Best Practices for Building Offline Apps"},"content":{"rendered":"<p><span style=\"font-size:16px\">(Photo by <a href=\"https:\/\/unsplash.com\/@jonflobrant?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText\">Jon Flobrant<\/a> on <a href=\"https:\/\/unsplash.com\/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText\">Unsplash<\/a>)<\/span><\/p>\n<p>It\u2019s a hard truth every software developer faces at some point: consistent Internet access is never guaranteed. WiFi is available everywhere nowadays, but service can be spotty or overloaded with connection requests (such as at large events or conferences). And, you can\u2019t stop your users from accessing your app while their connection is poor or non-existent. So as a developer, what do you do? <em>Embrace it.<\/em> Tame any concerns about building offline experiences by learning the fundamentals of Offline First.<\/p>\n<p><a href=\"http:\/\/offlinefirst.org\/\">Offline First<\/a> is the software engineering principle that assumes that apps can and will be used without a network connection at some point. This is especially important if you are designing for a mobile audience, who may go offline multiple times per day. Building Offline First apps increase the utility of your app and lead to better user satisfaction.<\/p>\n<p><!--more--><\/p>\n<h2>Offline First Mindset<\/h2>\n<p>Here are some of the things you can do to adopt an Offline First mindset.<\/p>\n<h3>Cache early, cache often<\/h3>\n<p>As the old saying goes, \u201cthe early bird <em>caches<\/em> the worm.\u201d \ud83d\ude09<\/p>\n<p>Bad jokes aside, <a href=\"https:\/\/developers.google.com\/web\/ilt\/pwa\/caching-files-with-service-worker\">cache your content<\/a> early and often while connections are stable to save yourself headaches when network connections are more intermittent.<\/p>\n<p>Caching content improves app performance since the now-local information is retrieved faster. Apps can be opened and closed while keeping their state that way as well. Check out some specific strategies for caching in the section on <em>Service Workers<\/em> below.<\/p>\n<h3>Identify the Non-Negotiable<\/h3>\n<p>On the flip side, some features are non-negotiable: you simply have to be connected to the internet to use them, such as location services.<\/p>\n<p>That\u2019s OK! Identify what features need an internet connection, then create messages or alerts informing those features\u2019 needs to users. Users will appreciate it because you\u2019ll take the guesswork out of understanding your app\u2019s limits when offline.<\/p>\n<h3>Be Ready for Conflict<\/h3>\n<p>Conflicting information can be a big problem. It may not be enough to simply merge and override all changes except the last time a user was online. Users should have a clear understanding of what happens when conflict is unavoidable. Once they know their options, they can choose what works for them.<\/p>\n<p>There are several solutions for handling conflicting information. It is up to you if you want the user to pick and choose which version to keep or to institute a &#8220;last write wins&#8221; rule. No matter what you choose, the app should handle this gracefully.<\/p>\n<h3>Know When to Apply Updates<\/h3>\n<p>Update too frequently and you may miss out on the benefits of being offline first. Update too slowly and you may create more conflicts than necessary and frustrate users.<\/p>\n<p>Knowing <em>what<\/em> to update along with <em>when<\/em> to update is an important consideration as well. Uploading only the most important data to your servers saves on storage and the time needed to upload.<\/p>\n<h3>Expect the Unexpected<\/h3>\n<p>Users are unpredictable. While you should test for a wide variety of situations you cannot account for everything. Track what users actually do with continued testing and surveying when users are offline.<\/p>\n<p>One personal example is my wife borrowing e-books from the library to read on her Kindle. Since borrowed books \u201cexpire\u201d after a set rental period, she keeps her Kindle offline as long as possible to ensure that if she needs more time to finish a book, she can do so without losing access to it.<\/p>\n<p>I do not know if the Kindle App&#8217;s developers anticipated people hanging onto books like that, but it is one example of people remaining offline as long as it takes to fit their use cases. The idea that everyone wants to constantly be online is one that should be tested against your own assumptions.<\/p>\n<p>Those are the concepts you need to be aware of. To put these concepts to work you&#8217;ll need the right tools. Fortunately, there are a number of existing tools that make it easier for developers to program with Offline First in mind.<\/p>\n<h2>Tools<\/h2>\n<h3>The Network Information API<\/h3>\n<p>The <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Network_Information_API\">Network Information API<\/a> is a critical tool for developers who want to ensure a smooth experience for users. Developers can use this API to create paths that open an app in online or offline mode from the moment a user opens the app. This cuts down on the errors a user sees when an app tries to find a connection but fails to open at all.<\/p>\n<p>The Network Information API even distinguishes between WiFi and Cellular networks,  allowing for fine-grained control over how content is served. The API also detects changes that allow apps to continue to function as a user moves from one network connection to another (like going in and out of subway tunnels).<\/p>\n<p>As an example, when a user is on a slower connection, they get an alert that performance may be degraded:<\/p>\n<pre><code class=\"language-javascript\">if (navigator.connection.effectiveType != &#039;4g&#039;) {\n  console.log(&#039;slow connection!&#039;);\n  \/\/ show modal dialog warning user that video will be loaded at lower resolution\n}\nelse {\n  console.log(&#039;ready to go!&#039;);\n  \/\/ immediate load video file\n}\n<\/code><\/pre>\n<p>Another option is Capacitor\u2019s <a href=\"https:\/\/capacitorjs.com\/docs\/apis\/network\">Network API<\/a>. It extends the Network Information API to provide even more useful features for web and mobile apps, such as monitoring the network for status changes, which your app can then react to.<\/p>\n<pre><code class=\"language-javascript\">import { Plugins } from &#039;@capacitor\/core&#039;;\n\nconst { Network } = Plugins;\n\nlet handler = Network.addListener(&#039;networkStatusChange&#039;, (status) =&gt; {\n  console.log(&quot;Network status changed&quot;, status);\n});\n\n\/\/ Get the current network status\nlet status = await Network.getStatus();\n\n\/\/ Example output:\n{\n  &quot;connected&quot;: true,\n  &quot;connectionType&quot;: &quot;wifi&quot;\n}\n<\/code><\/pre>\n<p>Monitoring the network with these two APIs is a crucial early step in making your offline experiences as smooth as possible.<\/p>\n<h3>Service Workers<\/h3>\n<p><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Service_Worker_API\/Using_Service_Workers\">Service Workers<\/a> let you control network requests, cache those requests, and then allow users to access cached content when offline.<\/p>\n<p>With the Cache API, it is simple to add resources with the built-in <em>add<\/em> method. For example:<\/p>\n<pre><code class=\"language-javascript\">const resource = caches.add(&#039;\/styles.css&#039;);\n<\/code><\/pre>\n<p>or you can use <em>addAll<\/em> to add multiple files, which uses a promise to ensure all the resources are cached and rejects the request if one is not loaded. For example:<\/p>\n<pre><code class=\"language-javascript\">const resources = caches.addAll(&#039;\/styles.css&#039;, &#039;\/fonts.woff&#039;, &#039;otherdata\/&#039;); \n<\/code><\/pre>\n<p>An event listener can fire after the app is installed which creates a cache and loads its contents ready to be used once installation is complete:<\/p>\n<pre><code class=\"language-javascript\">self.addEventListener(&#039;install&#039;, function(event) {\n  event.waitUntil(caches.open(&#039;static-files&#039;)\n    .then(function(cache) {\n       return caches.addAll([\n         &#039;\/css\/styles.css&#039;,\n         &#039;\/static\/images\/&#039;,\n         &#039;\/fonts\/fonts.woff&#039;\n       ]);\n    })\n  );\n }\n);\n<\/code><\/pre>\n<p>From there you can create additional functions that check for updates to the cached contents, delete out of date files, or add additional files as needed.<\/p>\n<h2>Offline Storage<\/h2>\n<p>How you save data locally is another important consideration. There are several solutions: localStorage, IndexedDB, and Ionic Offline Storage.<\/p>\n<h3>localStorage<\/h3>\n<p><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Window\/localStorage\">localStorage<\/a> contains built-in methods to create and store key -> value pairs. These are persisted offline and when a browser is closed. You can declare and retrieve data using <code>setItem<\/code> and <code>getItem<\/code>. The following code sets a &#8216;name&#8217; value and returns it when called.<\/p>\n<pre><code class=\"language-javascript\">var offlineStore = window.localStorage;\nofflineStore.setItem(&#039;name&#039;, &#039;John&#039;);\nvar name = localStorage.getItem(&#039;name&#039;);\n<\/code><\/pre>\n<p>Use the <code>clear<\/code> method to delete data.<\/p>\n<pre><code class=\"language-javascript\"> localStorage.clear(&#039;name&#039;);\n<\/code><\/pre>\n<h3>IndexedDB<\/h3>\n<p>localStorage is great for storing strings, but what if you want to store more complex data like images or blobs? That&#8217;s where <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/IndexedDB_API\">IndexedDB<\/a> comes in. IndexedDB lets you create a new local database connection and map objects to the schema you provide. It requires more code than localStorage but you can take advantage of more features.<\/p>\n<p>When creating an IndexedDB you create a &#8220;store&#8221; for your data and IndexedDB lets you go from there. Here&#8217;s an example of creating an IndexedDB of books and their authors:<\/p>\n<pre><code class=\"language-javascript\">\/\/ creates a new version of the database if needed\nconst dbName = &quot;books_db&quot;;\nlet request = window.indexedDB.open(dbName, 1),db,tx,store, index;\n\nrequest.onupgradeneeded = function() {\n  let db = request.result,\n  store = db.createObjectStore(&quot;booksStore&quot;, { keyPath: &quot;isbn&quot; }), \n  index = store.createIndex(&quot;title&quot;, &quot;title&quot;);\n} \n\nrequest.onsuccess = function() {\n  \/\/ db where data goes\n  db = request.result;\n\n  \/\/ defines the transactions allowed\n  tx = db.transaction(&quot;booksStore&quot;, &quot;readwrite&quot;);\n\n  \/\/ the store you create for the data that uses the &#039;isbn&#039; key.\n  store = tx.objectStore(&quot;booksStore&quot;);\n\n  \/\/ an index you can use to search data\n  index = store.index(&quot;title&quot;); \n\n  \/\/ insert data\n  store.put({isbn: 1234, title: &quot;Moby Dick&quot;, author: &quot;Herman Melville&quot;});\n  store.put({isbn: 4321, title: &quot;Emma&quot;, author: &quot;Jane Austen&quot;}); \n\n  \/\/ retrieve data\n  let book1 = store.get(1234);\n  book1.onsuccess = function() {\n    console.log(book1.result);\n  } \n\n  \/\/ close the database connection once the transaction has completed\n  tx.oncomplete = function() {\n    db.close();\n  };\n};\n<\/code><\/pre>\n<p>You can learn more by reading the <a href=\"https:\/\/www.w3.org\/TR\/IndexedDB\/#introduction\">IndexedDB docs<\/a>. There are other solutions like <a href=\"https:\/\/github.com\/localForage\/localForage\">localForage<\/a> which is a wrapper API for IndexedDB that aims to make it easier to work with.<\/p>\n<h3>Ionic\u2019s Offline Storage solution<\/h3>\n<p>For more advanced, robust data storage needs, check out Ionic\u2019s <a href=\"https:\/\/ionic.io\/products\/offline-storage\">Offline Storage<\/a> solution. It\u2019s a cross-platform data storage system that works on iOS and Android and powered by SQLite (a SQL database engine). Since it provides a performance-optimized query engine and on-device data encryption (256-bit AES), it\u2019s great for data-driven apps.<\/p>\n<p>Since it\u2019s based on the industry-standard SQL, it\u2019s easy for developers to add to their project. For example, creating a new table then inserting initial data:<\/p>\n<pre><code class=\"language-javascript\">this.database.transaction((tx) =&gt; {\n  tx.executeSql(&#039;CREATE TABLE IF NOT EXISTS software (name, company)&#039;);\n  tx.executeSql(&#039;INSERT INTO software VALUES (?,?)&#039;, [&#039;offline&#039;, &quot;ionic&quot;]);\n  tx.executeSql(&#039;INSERT INTO software VALUES (?,?)&#039;, [&#039;auth-connect&#039;, &quot;ionic&quot;]);\n});\n<\/code><\/pre>\n<p>Querying data involves writing SELECT statements then looping through the data results:<\/p>\n<pre><code class=\"language-javascript\">this.database.transaction((tx) =&gt; {\n  tx.executeSql(&quot;SELECT * from software&quot;, [], (tx, result) =&gt; {\n    \/\/ Rows is an array of results. Use zero-based indexing to access\n    \/\/ each element in the result set: item(0), item(1), etc. \n    for (let i = 0; i &lt; result.rows.length; i++) {\n      \/\/ { name: &quot;offline-storage&quot;, company: &quot;ionic&quot;, type: &quot;native&quot;, version: &quot;2.0&quot; }\n      console.log(result.rows.item(i));\n\n      \/\/ ionic\n      console.log(result.rows.item(i).company);\n    }\n  });\n});\n<\/code><\/pre>\n<p>Learn more about <a href=\"https:\/\/ionic.io\/products\/offline-storage\">building fast, data-driven apps here<\/a>.<\/p>\n<h3>Availability Messages<\/h3>\n<p>It&#8217;s important to communicate with users when their offline experience changes. Your app should be proactive about letting users know when they are online or offline. Use network event listeners to detect changes in a user\u2019s connection and send messages letting a user know that not all features may be available.<\/p>\n<p>The <a href=\"https:\/\/ionicframework.com\/docs\/api\/toast\">Ionic Framework Toast Component<\/a> is one tool that can help you set availability messages quickly and easily. Once your app detects that it is offline use a toast to notify the user:<\/p>\n<pre><code class=\"language-javascript\">\/\/ Display a toast message if the network connection goes offline\nasync function showOfflineToast() {  \n  const toast = document.createElement(&#039;ion-toast&#039;);  \n\n  \/\/ Display the message that the User is now offline. \n  toast.message = &#039;The latest chat messages will appear when you are back online.&#039;;\n\n  \/\/ Display the toast message for 2 seconds\n  toast.duration = 2000;\n}\n<\/code><\/pre>\n<h2>Start Building Offline Apps<\/h2>\n<p>Users will go offline at some point, due to intermittent network connectivity or their location (remote areas or thick-walled buildings for example). They may simply be concerned about limiting bandwidth usage to save on costs, preferring offline modes over the need for constant connection. Regardless of the reason, we must account for these scenarios.<\/p>\n<p>If this seems challenging to get started with Offline First, don\u2019t worry. Use the following checklist to ensure that you are on the right track when developing an Offline-First experience. Your users will thank you!<\/p>\n<ol>\n<li>Are you designing for an Offline-First experience? <\/li>\n<li>Are you checking for network connections using the Network API? What type of connections are you detecting? <\/li>\n<li>Have you configured a service worker to serve offline content? <\/li>\n<li>What features cannot work without an internet connection? How will you let users know what is or is not possible offline?<\/li>\n<li>What are your storage solutions for offline content? Will you rely on localStorage or a more robust system like IndexedDB? <\/li>\n<\/ol>\n<p>What tips and tricks do you use when building offline apps? Let us know below.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>(Photo by Jon Flobrant on Unsplash) It\u2019s a hard truth every software developer faces at some point: consistent Internet access is never guaranteed. WiFi is available everywhere nowadays, but service can be spotty or overloaded with connection requests (such as at large events or conferences). And, you can\u2019t stop your users from accessing your app [&hellip;]<\/p>\n","protected":false},"author":78,"featured_media":3568,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"publish_to_discourse":"1","publish_post_category":"30","wpdc_auto_publish_overridden":"","wpdc_topic_tags":"","wpdc_pin_topic":"","wpdc_pin_until":"","discourse_post_id":"491906","discourse_permalink":"https:\/\/forum.ionicframework.com\/t\/best-practices-for-building-offline-apps\/201507","wpdc_publishing_response":"","wpdc_publishing_error":"","_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[121],"tags":[173,200,78],"class_list":["post-3565","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-engineering","tag-mobile","tag-offline","tag-ux"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v23.0 (Yoast SEO v23.0) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Best Practices for Building Offline Apps - Ionic Blog<\/title>\n<meta name=\"description\" content=\"Internet access is never guaranteed. Learn how build great offline app experiences with the Offline First approach.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/ionic.io\/blog\/best-practices-for-building-offline-apps\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Best Practices for Building Offline Apps\" \/>\n<meta property=\"og:description\" content=\"Internet access is never guaranteed. Learn how build great offline app experiences with the Offline First approach.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/ionic.io\/blog\/best-practices-for-building-offline-apps\" \/>\n<meta property=\"og:site_name\" content=\"Ionic Blog\" \/>\n<meta property=\"article:published_time\" content=\"2020-12-18T18:30:08+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/12\/jon-flobrant-MSbqTMftCy0-unsplash-scaled.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"2560\" \/>\n\t<meta property=\"og:image:height\" content=\"1707\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Canaan Merchant\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@ionicframework\" \/>\n<meta name=\"twitter:site\" content=\"@ionicframework\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Canaan Merchant\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"10 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/ionic.io\/blog\/best-practices-for-building-offline-apps#article\",\"isPartOf\":{\"@id\":\"https:\/\/ionic.io\/blog\/best-practices-for-building-offline-apps\"},\"author\":{\"name\":\"Canaan Merchant\",\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/person\/322c7d50fb25b9c270ebdf16cc47ef15\"},\"headline\":\"Best Practices for Building Offline Apps\",\"datePublished\":\"2020-12-18T18:30:08+00:00\",\"dateModified\":\"2020-12-18T18:30:08+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/ionic.io\/blog\/best-practices-for-building-offline-apps\"},\"wordCount\":1565,\"commentCount\":172,\"publisher\":{\"@id\":\"https:\/\/ionic.io\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/ionic.io\/blog\/best-practices-for-building-offline-apps#primaryimage\"},\"thumbnailUrl\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/12\/jon-flobrant-MSbqTMftCy0-unsplash-scaled.jpg\",\"keywords\":[\"mobile\",\"offline\",\"UX\"],\"articleSection\":[\"Engineering\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/ionic.io\/blog\/best-practices-for-building-offline-apps#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/ionic.io\/blog\/best-practices-for-building-offline-apps\",\"url\":\"https:\/\/ionic.io\/blog\/best-practices-for-building-offline-apps\",\"name\":\"Best Practices for Building Offline Apps - Ionic Blog\",\"isPartOf\":{\"@id\":\"https:\/\/ionic.io\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/ionic.io\/blog\/best-practices-for-building-offline-apps#primaryimage\"},\"image\":{\"@id\":\"https:\/\/ionic.io\/blog\/best-practices-for-building-offline-apps#primaryimage\"},\"thumbnailUrl\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/12\/jon-flobrant-MSbqTMftCy0-unsplash-scaled.jpg\",\"datePublished\":\"2020-12-18T18:30:08+00:00\",\"dateModified\":\"2020-12-18T18:30:08+00:00\",\"description\":\"Internet access is never guaranteed. Learn how build great offline app experiences with the Offline First approach.\",\"breadcrumb\":{\"@id\":\"https:\/\/ionic.io\/blog\/best-practices-for-building-offline-apps#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/ionic.io\/blog\/best-practices-for-building-offline-apps\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/ionic.io\/blog\/best-practices-for-building-offline-apps#primaryimage\",\"url\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/12\/jon-flobrant-MSbqTMftCy0-unsplash-scaled.jpg\",\"contentUrl\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/12\/jon-flobrant-MSbqTMftCy0-unsplash-scaled.jpg\",\"width\":2560,\"height\":1707,\"caption\":\"offline-lighthouse\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/ionic.io\/blog\/best-practices-for-building-offline-apps#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/ionic.io\/blog\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Best Practices for Building Offline Apps\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/ionic.io\/blog\/#website\",\"url\":\"https:\/\/ionic.io\/blog\/\",\"name\":\"ionic.io\/blog\",\"description\":\"Build amazing native and progressive web apps with the web\",\"publisher\":{\"@id\":\"https:\/\/ionic.io\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/ionic.io\/blog\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/ionic.io\/blog\/#organization\",\"name\":\"Ionic\",\"url\":\"https:\/\/ionic.io\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/10\/white-on-color.png\",\"contentUrl\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/10\/white-on-color.png\",\"width\":1920,\"height\":854,\"caption\":\"Ionic\"},\"image\":{\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/x.com\/ionicframework\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/person\/322c7d50fb25b9c270ebdf16cc47ef15\",\"name\":\"Canaan Merchant\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/12\/canaan-150x150.png\",\"contentUrl\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/12\/canaan-150x150.png\",\"caption\":\"Canaan Merchant\"},\"url\":\"https:\/\/ionic.io\/blog\/author\/canaan\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Best Practices for Building Offline Apps - Ionic Blog","description":"Internet access is never guaranteed. Learn how build great offline app experiences with the Offline First approach.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/ionic.io\/blog\/best-practices-for-building-offline-apps","og_locale":"en_US","og_type":"article","og_title":"Best Practices for Building Offline Apps","og_description":"Internet access is never guaranteed. Learn how build great offline app experiences with the Offline First approach.","og_url":"https:\/\/ionic.io\/blog\/best-practices-for-building-offline-apps","og_site_name":"Ionic Blog","article_published_time":"2020-12-18T18:30:08+00:00","og_image":[{"width":2560,"height":1707,"url":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/12\/jon-flobrant-MSbqTMftCy0-unsplash-scaled.jpg","type":"image\/jpeg"}],"author":"Canaan Merchant","twitter_card":"summary_large_image","twitter_creator":"@ionicframework","twitter_site":"@ionicframework","twitter_misc":{"Written by":"Canaan Merchant","Est. reading time":"10 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/ionic.io\/blog\/best-practices-for-building-offline-apps#article","isPartOf":{"@id":"https:\/\/ionic.io\/blog\/best-practices-for-building-offline-apps"},"author":{"name":"Canaan Merchant","@id":"https:\/\/ionic.io\/blog\/#\/schema\/person\/322c7d50fb25b9c270ebdf16cc47ef15"},"headline":"Best Practices for Building Offline Apps","datePublished":"2020-12-18T18:30:08+00:00","dateModified":"2020-12-18T18:30:08+00:00","mainEntityOfPage":{"@id":"https:\/\/ionic.io\/blog\/best-practices-for-building-offline-apps"},"wordCount":1565,"commentCount":172,"publisher":{"@id":"https:\/\/ionic.io\/blog\/#organization"},"image":{"@id":"https:\/\/ionic.io\/blog\/best-practices-for-building-offline-apps#primaryimage"},"thumbnailUrl":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/12\/jon-flobrant-MSbqTMftCy0-unsplash-scaled.jpg","keywords":["mobile","offline","UX"],"articleSection":["Engineering"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/ionic.io\/blog\/best-practices-for-building-offline-apps#respond"]}]},{"@type":"WebPage","@id":"https:\/\/ionic.io\/blog\/best-practices-for-building-offline-apps","url":"https:\/\/ionic.io\/blog\/best-practices-for-building-offline-apps","name":"Best Practices for Building Offline Apps - Ionic Blog","isPartOf":{"@id":"https:\/\/ionic.io\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/ionic.io\/blog\/best-practices-for-building-offline-apps#primaryimage"},"image":{"@id":"https:\/\/ionic.io\/blog\/best-practices-for-building-offline-apps#primaryimage"},"thumbnailUrl":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/12\/jon-flobrant-MSbqTMftCy0-unsplash-scaled.jpg","datePublished":"2020-12-18T18:30:08+00:00","dateModified":"2020-12-18T18:30:08+00:00","description":"Internet access is never guaranteed. Learn how build great offline app experiences with the Offline First approach.","breadcrumb":{"@id":"https:\/\/ionic.io\/blog\/best-practices-for-building-offline-apps#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/ionic.io\/blog\/best-practices-for-building-offline-apps"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/ionic.io\/blog\/best-practices-for-building-offline-apps#primaryimage","url":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/12\/jon-flobrant-MSbqTMftCy0-unsplash-scaled.jpg","contentUrl":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/12\/jon-flobrant-MSbqTMftCy0-unsplash-scaled.jpg","width":2560,"height":1707,"caption":"offline-lighthouse"},{"@type":"BreadcrumbList","@id":"https:\/\/ionic.io\/blog\/best-practices-for-building-offline-apps#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/ionic.io\/blog"},{"@type":"ListItem","position":2,"name":"Best Practices for Building Offline Apps"}]},{"@type":"WebSite","@id":"https:\/\/ionic.io\/blog\/#website","url":"https:\/\/ionic.io\/blog\/","name":"ionic.io\/blog","description":"Build amazing native and progressive web apps with the web","publisher":{"@id":"https:\/\/ionic.io\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/ionic.io\/blog\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/ionic.io\/blog\/#organization","name":"Ionic","url":"https:\/\/ionic.io\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/ionic.io\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/10\/white-on-color.png","contentUrl":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/10\/white-on-color.png","width":1920,"height":854,"caption":"Ionic"},"image":{"@id":"https:\/\/ionic.io\/blog\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/x.com\/ionicframework"]},{"@type":"Person","@id":"https:\/\/ionic.io\/blog\/#\/schema\/person\/322c7d50fb25b9c270ebdf16cc47ef15","name":"Canaan Merchant","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/ionic.io\/blog\/#\/schema\/person\/image\/","url":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/12\/canaan-150x150.png","contentUrl":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/12\/canaan-150x150.png","caption":"Canaan Merchant"},"url":"https:\/\/ionic.io\/blog\/author\/canaan"}]}},"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/12\/jon-flobrant-MSbqTMftCy0-unsplash-scaled.jpg","_links":{"self":[{"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/posts\/3565","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/users\/78"}],"replies":[{"embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/comments?post=3565"}],"version-history":[{"count":0,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/posts\/3565\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/media\/3568"}],"wp:attachment":[{"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/media?parent=3565"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/categories?post=3565"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/tags?post=3565"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}