-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
Copy pathrxdb-tradeoffs.html
68 lines (66 loc) · 23.7 KB
/
rxdb-tradeoffs.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<!doctype html>
<html lang="en" dir="ltr" class="docs-wrapper plugin-docs plugin-id-default docs-version-current docs-doc-page docs-doc-id-rxdb-tradeoffs" data-has-hydrated="false">
<head>
<meta charset="UTF-8">
<meta name="generator" content="Docusaurus v3.7.0">
<title data-rh="true">RxDB Tradeoffs - Why NoSQL Triumphs on the Client | RxDB - JavaScript Database</title><meta data-rh="true" name="viewport" content="width=device-width,initial-scale=1"><meta data-rh="true" name="twitter:card" content="summary_large_image"><meta data-rh="true" property="og:image" content="https://rxdb.info/img/rxdb_social_card.png"><meta data-rh="true" name="twitter:image" content="https://rxdb.info/img/rxdb_social_card.png"><meta data-rh="true" property="og:url" content="https://rxdb.info/rxdb-tradeoffs.html"><meta data-rh="true" property="og:locale" content="en"><meta data-rh="true" name="docusaurus_locale" content="en"><meta data-rh="true" name="docsearch:language" content="en"><meta data-rh="true" name="docusaurus_version" content="current"><meta data-rh="true" name="docusaurus_tag" content="docs-default-current"><meta data-rh="true" name="docsearch:version" content="current"><meta data-rh="true" name="docsearch:docusaurus_tag" content="docs-default-current"><meta data-rh="true" property="og:title" content="RxDB Tradeoffs - Why NoSQL Triumphs on the Client | RxDB - JavaScript Database"><meta data-rh="true" name="description" content="Uncover RxDB's approach to modern database needs. From JSON-based queries to conflict handling without transactions, learn RxDB's unique tradeoffs."><meta data-rh="true" property="og:description" content="Uncover RxDB's approach to modern database needs. From JSON-based queries to conflict handling without transactions, learn RxDB's unique tradeoffs."><link data-rh="true" rel="icon" href="/img/favicon.ico"><link data-rh="true" rel="canonical" href="https://rxdb.info/rxdb-tradeoffs.html"><link data-rh="true" rel="alternate" href="https://rxdb.info/rxdb-tradeoffs.html" hreflang="en"><link data-rh="true" rel="alternate" href="https://rxdb.info/rxdb-tradeoffs.html" hreflang="x-default"><link rel="preconnect" href="https://www.google-analytics.com">
<link rel="preconnect" href="https://www.googletagmanager.com">
<script async src="https://www.googletagmanager.com/gtag/js?id=G-62D63SY3S0"></script>
<script>function gtag(){dataLayer.push(arguments)}window.dataLayer=window.dataLayer||[],gtag("js",new Date),gtag("config","G-62D63SY3S0",{})</script>
<link rel="preconnect" href="https://www.googletagmanager.com">
<script>window.dataLayer=window.dataLayer||[]</script>
<script>!function(e,t,a,n){e[n]=e[n]||[],e[n].push({"gtm.start":(new Date).getTime(),event:"gtm.js"});var g=t.getElementsByTagName(a)[0],m=t.createElement(a);m.async=!0,m.src="https://www.googletagmanager.com/gtm.js?id=GTM-PL63TR5",g.parentNode.insertBefore(m,g)}(window,document,"script","dataLayer")</script><link rel="stylesheet" href="/assets/css/styles.bc483400.css">
<script src="/assets/js/runtime~main.68ae4ffa.js" defer="defer"></script>
<script src="/assets/js/main.90e814da.js" defer="defer"></script>
</head>
<body class="navigation-with-keyboard">
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-PL63TR5" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<script>!function(){function t(t){document.documentElement.setAttribute("data-theme",t)}var e=function(){try{return new URLSearchParams(window.location.search).get("docusaurus-theme")}catch(t){}}()||function(){try{return window.localStorage.getItem("theme")}catch(t){}}();t(null!==e?e:"dark")}(),function(){try{const n=new URLSearchParams(window.location.search).entries();for(var[t,e]of n)if(t.startsWith("docusaurus-data-")){var a=t.replace("docusaurus-data-","data-");document.documentElement.setAttribute(a,e)}}catch(t){}}()</script><div id="__docusaurus"><div role="region" aria-label="Skip to main content"><a class="skipToContent_fXgn" href="#__docusaurus_skipToContent_fallback">Skip to main content</a></div><nav aria-label="Main" class="navbar navbar--fixed-top"><div class="navbar__inner"><div class="navbar__items"><button aria-label="Toggle navigation bar" aria-expanded="false" class="navbar__toggle clean-btn" type="button"><svg width="30" height="30" viewBox="0 0 30 30" aria-hidden="true"><path stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2" d="M4 7h22M4 15h22M4 23h22"></path></svg></button><a class="navbar__brand" href="/"><div class="navbar__logo"><img src="/files/logo/logo.svg" alt="RxDB Logo" class="themedComponent_mlkZ themedComponent--light_NVdE"><img src="/files/logo/logo.svg" alt="RxDB Logo" class="themedComponent_mlkZ themedComponent--dark_xIcU"></div><b class="navbar__title text--truncate">RxDB</b></a></div><div class="navbar__items navbar__items--right"><a class="navbar__item navbar__link" href="/consulting/">Support</a><a class="navbar__item navbar__link" href="/premium/">Premium</a><a class="navbar__item navbar__link" href="/overview.html">Docs</a><div class="navbarSearchContainer_Bca1"></div></div></div><div role="presentation" class="navbar-sidebar__backdrop"></div></nav><div style="position:fixed;display:block;z-index:10;height:1.5px;background-color:var(--color-top);top:62.5px;border-top-right-radius:2px;border-bottom-right-radius:2px;width:0%"></div><div id="__docusaurus_skipToContent_fallback" class="main-wrapper mainWrapper_z2l0"><div class="docsWrapper_hBAB"><button aria-label="Scroll back to top" class="clean-btn theme-back-to-top-button backToTopButton_sjWU" type="button"></button><div class="docRoot_UBD9"><main class="docMainContainer_TBSr docMainContainerEnhanced_lQrH"><div class="container padding-top--md padding-bottom--lg"><div class="row"><div class="col docItemCol_VOVn"><div class="docItemContainer_Djhp"><article><div class="tocCollapsible_ETCw theme-doc-toc-mobile tocMobile_ITEo"><button type="button" class="clean-btn tocCollapsibleButton_TO0P">On this page</button></div><div class="theme-doc-markdown markdown"><header><h1>RxDB Tradeoffs</h1></header>
<p><a href="https://rxdb.info" target="_blank" rel="noopener noreferrer">RxDB</a> is client-side, <a href="/offline-first.html">offline first</a> Database for JavaScript applications.
While RxDB could be used on the server side, most people use it on the client side together with an UI based application.
Therefore RxDB was optimized for client side applications and had to take completely different tradeoffs than what a server side database would do.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="why-not-sql-syntax">Why not SQL syntax<a href="#why-not-sql-syntax" class="hash-link" aria-label="Direct link to Why not SQL syntax" title="Direct link to Why not SQL syntax"></a></h2>
<p>When you ask people which database they would want for browsers, the most answer I hear is <em>something SQL based like SQLite</em>.
This makes sense, SQL is a query language that most developers had learned in school/university and it is reusable across various database solutions.
But for RxDB (and other client side databases), using SQL is not a good option and instead it operates on document writes and the JSON based <strong>Mango-query</strong> syntax for querying.</p>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// A Mango Query</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> query </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> selector</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> age</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> $gt</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">10</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> lastName</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'foo'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> sort</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> age</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'asc'</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="sql-is-made-for-database-servers">SQL is made for database servers<a href="#sql-is-made-for-database-servers" class="hash-link" aria-label="Direct link to SQL is made for database servers" title="Direct link to SQL is made for database servers"></a></h3>
<p>SQL is made to be used to run operations against a database server. You send a SQL string like <code>SELECT SUM(column_name)...</code> to the database server and the server then runs all operations required to calculate the result and only send back that result.
This saves performance on the application side and ensures that the application itself is not blocked.</p>
<p>But RxDB is a client-side database that runs <strong>inside</strong> of the application. There is no performance difference if the <code>SUM()</code> query is run inside of the database or at the application level where a <code>Array.reduce()</code> call calculates the result.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="typescript-support">Typescript support<a href="#typescript-support" class="hash-link" aria-label="Direct link to Typescript support" title="Direct link to Typescript support"></a></h3>
<p>SQL is <code>string</code> based and therefore you need additional IDE tooling to ensure that your written database code is valid.
Using the Mango Query syntax instead, TypeScript can be used validate the queries and to autocomplete code and knows which fields do exist and which do not. By doing so, the correctness of queries can be ensured at compile-time instead of run-time.</p>
<p align="center"><img src="./files/typescript-query-validation.png" alt="TypeScript Query Validation"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="composeable-queries">Composeable queries<a href="#composeable-queries" class="hash-link" aria-label="Direct link to Composeable queries" title="Direct link to Composeable queries"></a></h3>
<p>By using JSON based Mango Queries, it is easy to compose queries in plain JavaScript.
For example if you have any given query and want to add the condition <code>user MUST BE 'foobar'</code>, you can just add the condition to the selector without having to parse and understand a complex SQL string.</p>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">query</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">selector</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">user </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'foobar'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Even merging the selectors of multiple queries is not a problem:</p>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">queryA</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">selector </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> $and</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> queryA</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">selector</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> queryB</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">selector</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="why-document-based-nosql">Why Document based (NoSQL)<a href="#why-document-based-nosql" class="hash-link" aria-label="Direct link to Why Document based (NoSQL)" title="Direct link to Why Document based (NoSQL)"></a></h2>
<p>Like other NoSQL databases, RxDB operates data on document level. It has no concept of tables, rows and columns. Instead we have collections, documents and fields.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="javascript-is-made-to-work-with-objects">Javascript is made to work with objects<a href="#javascript-is-made-to-work-with-objects" class="hash-link" aria-label="Direct link to Javascript is made to work with objects" title="Direct link to Javascript is made to work with objects"></a></h3>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="caching">Caching<a href="#caching" class="hash-link" aria-label="Direct link to Caching" title="Direct link to Caching"></a></h3>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="eventreduce">EventReduce<a href="#eventreduce" class="hash-link" aria-label="Direct link to EventReduce" title="Direct link to EventReduce"></a></h3>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="easier-to-use-with-typescript">Easier to use with typescript<a href="#easier-to-use-with-typescript" class="hash-link" aria-label="Direct link to Easier to use with typescript" title="Direct link to Easier to use with typescript"></a></h3>
<p>Because of the document based approach, TypeScript can know the exact type of the query response while a SQL query could return anything from a number over a set of rows or a complex construct.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="why-no-transactions">Why no transactions<a href="#why-no-transactions" class="hash-link" aria-label="Direct link to Why no transactions" title="Direct link to Why no transactions"></a></h2>
<ul>
<li>Does not work with offline-first</li>
<li>Does not work with multi-tab</li>
<li>Easier conflict handling on document level</li>
</ul>
<p>-- Instead of transactions, rxdb works with revisions</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="why-no-relations">Why no relations<a href="#why-no-relations" class="hash-link" aria-label="Direct link to Why no relations" title="Direct link to Why no relations"></a></h2>
<ul>
<li>Does not work with easy replication</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="why-is-a-schema-required">Why is a schema required<a href="#why-is-a-schema-required" class="hash-link" aria-label="Direct link to Why is a schema required" title="Direct link to Why is a schema required"></a></h2>
<ul>
<li>migration of data on clients is hard</li>
<li>Why jsonschema</li>
</ul>
<h2></h2></div></article><nav class="pagination-nav docusaurus-mt-lg" aria-label="Docs pages"></nav></div></div><div class="col col--3"><div class="tableOfContents_bqdL thin-scrollbar theme-doc-toc-desktop"><ul class="table-of-contents table-of-contents__left-border"><li><a href="#why-not-sql-syntax" class="table-of-contents__link toc-highlight">Why not SQL syntax</a><ul><li><a href="#sql-is-made-for-database-servers" class="table-of-contents__link toc-highlight">SQL is made for database servers</a></li><li><a href="#typescript-support" class="table-of-contents__link toc-highlight">Typescript support</a></li><li><a href="#composeable-queries" class="table-of-contents__link toc-highlight">Composeable queries</a></li></ul></li><li><a href="#why-document-based-nosql" class="table-of-contents__link toc-highlight">Why Document based (NoSQL)</a><ul><li><a href="#javascript-is-made-to-work-with-objects" class="table-of-contents__link toc-highlight">Javascript is made to work with objects</a></li><li><a href="#caching" class="table-of-contents__link toc-highlight">Caching</a></li><li><a href="#eventreduce" class="table-of-contents__link toc-highlight">EventReduce</a></li><li><a href="#easier-to-use-with-typescript" class="table-of-contents__link toc-highlight">Easier to use with typescript</a></li></ul></li><li><a href="#why-no-transactions" class="table-of-contents__link toc-highlight">Why no transactions</a></li><li><a href="#why-no-relations" class="table-of-contents__link toc-highlight">Why no relations</a></li><li><a href="#why-is-a-schema-required" class="table-of-contents__link toc-highlight">Why is a schema required</a></li></ul></div></div></div></div></main></div></div></div><div class="block footer"><div class="footer-block"><div class="footer-links"><span><a variant="text" href="/" class="footer-logo-button"><img src="/files/logo/logo.svg" alt="RxDB" loading="lazy"><div>RxDB</div></a><div class="footer-community-links"><a variant="text" href="/chat/" target="_blank"><img src="/img/community-links/discord-logo.svg" alt="RxDB Discord" loading="lazy"></a><a variant="text" href="/code/" target="_blank"><img src="/img/community-links/github-logo.svg" alt="RxDB Github" loading="lazy"></a><a variant="text" href="https://twitter.com/intent/user?screen_name=rxdbjs" target="_blank"><img src="/img/community-links/x-logo.svg" alt="RxDB Twitter" loading="lazy"></a><a variant="text" href="https://www.linkedin.com/company/rxdb" target="_blank"><img src="/img/community-links/linkedin-logo.svg" alt="RxDB LinkedIn" loading="lazy"></a><a variant="text" href="https://stackoverflow.com/questions/tagged/rxdb" target="_blank"><img src="/img/community-links/stack-overflow-logo.svg" alt="RxDB Stack Overflow" loading="lazy"></a></div></span><div class="footer-nav-links"><a variant="text" href="/premium/" target="">Premium</a><a variant="text" href="/consulting/" target="">Support</a><a variant="text" href="/overview.html" target="">Documentation</a><a variant="text" href="/chat/" target="_blank">Discord</a><a variant="text" href="/code/" target="_blank">Github</a><a variant="text" href="https://twitter.com/intent/user?screen_name=rxdbjs" target="_blank">Twitter</a><a variant="text" href="https://www.linkedin.com/company/rxdb" target="_blank">LinkedIn</a></div></div><div class="footer-policy"><div><a variant="text" href="/legal-notice/" target="_blank">Legal Notice</a></div><span class="footer-rights">© 2025 RxDB. All rights reserved.</span></div><img class="footer-img desktop-img" src="/img/footer-column.svg" alt="columns" loading="lazy"></div></div><div class="call-to-action-popup"><div class="close"><div class="text">✕</div></div></div></div>
</body>
</html>