<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: doichizen</title>
    <description>The latest articles on DEV Community by doichizen (@doichizen).</description>
    <link>https://dev.to/doichizen</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4107293%2F1c3260ae-3164-42bf-bb16-b69fb9c7d172.jpg</url>
      <title>DEV Community: doichizen</title>
      <link>https://dev.to/doichizen</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/doichizen"/>
    <language>en</language>
    <item>
      <title>How to Make 恋みくじ Results Shareable Without Storing Personal Data</title>
      <dc:creator>doichizen</dc:creator>
      <pubDate>Thu, 03 Sep 2026 05:33:20 +0000</pubDate>
      <link>https://dev.to/doichizen/how-to-make-lian-mikuzi-results-shareable-without-storing-personal-data-3m1a</link>
      <guid>https://dev.to/doichizen/how-to-make-lian-mikuzi-results-shareable-without-storing-personal-data-3m1a</guid>
      <description>&lt;p&gt;This post explains one of the technical and privacy questions behind the project rather than presenting an independent review.&lt;/p&gt;

&lt;p&gt;A share button seems like a small feature.&lt;/p&gt;

&lt;p&gt;The visitor receives a result, presses “Share,” and sends it to a friend. From a product perspective, sharing can help an experience travel beyond its original website.&lt;/p&gt;

&lt;p&gt;But when the product involves personal emotions, the architecture behind that button deserves more attention.&lt;/p&gt;

&lt;p&gt;A  &lt;a href="https://www.ichizenn.com/koi-mikuji/" rel="noopener noreferrer"&gt;恋みくじ&lt;/a&gt;  is a Japanese-style love fortune. Before drawing one, a visitor may be thinking about an unspoken feeling, a delayed reply, reconciliation, or uncertainty about a relationship.&lt;/p&gt;

&lt;p&gt;The application does not need to know that private question.&lt;/p&gt;

&lt;p&gt;More importantly, the shared URL should never expose it.&lt;/p&gt;

&lt;p&gt;This creates an interesting engineering problem:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How can we make a personalized-looking result easy to save and share without storing the visitor’s identity, question, or relationship history?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is the privacy-first architecture I would use.&lt;/p&gt;

&lt;p&gt;Begin with a data boundary&lt;/p&gt;

&lt;p&gt;Before choosing a database or sharing library, define which information is allowed to leave the browser.&lt;/p&gt;

&lt;p&gt;For a 恋みくじ application, the data can be divided into three categories.&lt;/p&gt;

&lt;p&gt;Public content&lt;/p&gt;

&lt;p&gt;This is content that already belongs to the application:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fortune result ID&lt;/li&gt;
&lt;li&gt;Published headline&lt;/li&gt;
&lt;li&gt;Published interpretation&lt;/li&gt;
&lt;li&gt;Interface language&lt;/li&gt;
&lt;li&gt;Public artwork&lt;/li&gt;
&lt;li&gt;Product URL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Private context&lt;/p&gt;

&lt;p&gt;This exists only to help the visitor think about their situation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The person’s name&lt;/li&gt;
&lt;li&gt;The visitor’s romantic question&lt;/li&gt;
&lt;li&gt;Relationship history&lt;/li&gt;
&lt;li&gt;Message contents&lt;/li&gt;
&lt;li&gt;Personal notes&lt;/li&gt;
&lt;li&gt;Reasons for drawing the fortune&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Operational data&lt;/p&gt;

&lt;p&gt;This may be collected in a limited form to operate the service:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Page load count&lt;/li&gt;
&lt;li&gt;Draw completion&lt;/li&gt;
&lt;li&gt;Error information&lt;/li&gt;
&lt;li&gt;Selected language&lt;/li&gt;
&lt;li&gt;Performance measurements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The basic rule is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A shareable result should be constructed from public content, not private context.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the visitor selects “waiting for a reply,” the application may use that choice locally to select a suitable category of results. The shared page does not need to say who the visitor is waiting for or what message was sent.&lt;/p&gt;

&lt;p&gt;The URL is not private storage&lt;/p&gt;

&lt;p&gt;One of the easiest implementation mistakes is placing the entire interaction inside a query string.&lt;/p&gt;

&lt;p&gt;text&lt;br&gt;
example.com/result&lt;br&gt;
  ?name=Alex&lt;br&gt;
  &amp;amp;question=WillTheyReply&lt;br&gt;
  &amp;amp;situation=Reconciliation&lt;br&gt;
  &amp;amp;result=GoodLuck&lt;/p&gt;

&lt;p&gt;This URL can leak through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Browser history&lt;/li&gt;
&lt;li&gt;Server logs&lt;/li&gt;
&lt;li&gt;Analytics systems&lt;/li&gt;
&lt;li&gt;Screenshots&lt;/li&gt;
&lt;li&gt;Referrer headers&lt;/li&gt;
&lt;li&gt;Messaging applications&lt;/li&gt;
&lt;li&gt;Link preview services&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Removing the person’s name but leaving a detailed romantic question in the URL does not solve the problem.&lt;/p&gt;

&lt;p&gt;A safer URL contains only a public result identifier:&lt;/p&gt;

&lt;p&gt;text&lt;br&gt;
example.com/fortune/en/gentle-patience-014&lt;/p&gt;

&lt;p&gt;Here, &lt;code&gt;gentle-patience-014&lt;/code&gt; identifies a published result from the application’s public catalog.&lt;/p&gt;

&lt;p&gt;It does not identify the visitor.&lt;/p&gt;

&lt;p&gt;typescript&lt;br&gt;
type PublicShareReference = {&lt;br&gt;
  resultId: string;&lt;br&gt;
  locale: string;&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;The route can use these two values to look up the correct public content.&lt;/p&gt;

&lt;p&gt;typescript&lt;br&gt;
async function loadSharedFortune(&lt;br&gt;
  resultId: string,&lt;br&gt;
  locale: string&lt;br&gt;
) {&lt;br&gt;
  return fortuneRepository.findPublished({&lt;br&gt;
    id: resultId,&lt;br&gt;
    locale&lt;br&gt;
  });&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;If someone changes the ID manually, the worst outcome should be that they see another public fortune or a 404 page.&lt;/p&gt;

&lt;p&gt;A result ID must not provide access to private records.&lt;/p&gt;

&lt;p&gt;Do not confuse obscurity with privacy&lt;/p&gt;

&lt;p&gt;A long random URL may look private:&lt;/p&gt;

&lt;p&gt;text&lt;br&gt;
example.com/r/7JkP2mQx9A&lt;/p&gt;

&lt;p&gt;But an unguessable identifier does not automatically make the underlying system privacy-friendly.&lt;/p&gt;

&lt;p&gt;If the token points to a database record containing a visitor’s name, question, device ID, and complete interaction history, the system is still storing personal data.&lt;/p&gt;

&lt;p&gt;Opaque IDs are useful when a private server-side record is genuinely required. They should not be used to justify collecting information the feature does not need.&lt;/p&gt;

&lt;p&gt;For a finite catalog of 恋みくじ results, a public result ID is often enough.&lt;/p&gt;

&lt;p&gt;No visitor record is necessary.&lt;/p&gt;

&lt;p&gt;Use a small share object&lt;/p&gt;

&lt;p&gt;The browser only needs a title, short text, and public URL.&lt;/p&gt;

&lt;p&gt;typescript&lt;br&gt;
type ShareableFortune = {&lt;br&gt;
  title: string;&lt;br&gt;
  summary: string;&lt;br&gt;
  url: string;&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;It can be constructed from published content:&lt;/p&gt;

&lt;p&gt;typescript&lt;br&gt;
function createShareData(&lt;br&gt;
  result: FortuneResult,&lt;br&gt;
  locale: string&lt;br&gt;
): ShareableFortune {&lt;br&gt;
  return {&lt;br&gt;
    title: &lt;code&gt;${result.headline} — 恋みくじ&lt;/code&gt;,&lt;br&gt;
    summary: result.shortInterpretation,&lt;br&gt;
    url: &lt;code&gt;https://example.com/fortune/${locale}/${result.id}&lt;/code&gt;&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Notice what is missing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No account ID&lt;/li&gt;
&lt;li&gt;No email address&lt;/li&gt;
&lt;li&gt;No personal question&lt;/li&gt;
&lt;li&gt;No relationship status&lt;/li&gt;
&lt;li&gt;No session token&lt;/li&gt;
&lt;li&gt;No analytics identifier&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result still feels personal because it appeared after an intentional interaction. It does not need to contain personal data to preserve that feeling.&lt;/p&gt;

&lt;p&gt;Use the Web Share API as progressive enhancement&lt;/p&gt;

&lt;p&gt;On compatible browsers, the Web Share API provides a native sharing interface.&lt;/p&gt;

&lt;p&gt;typescript&lt;br&gt;
async function shareFortune(&lt;br&gt;
  shareData: ShareableFortune&lt;br&gt;
): Promise {&lt;br&gt;
  if (navigator.share) {&lt;br&gt;
    await navigator.share({&lt;br&gt;
      title: shareData.title,&lt;br&gt;
      text: shareData.summary,&lt;br&gt;
      url: shareData.url&lt;br&gt;
    });&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;await navigator.clipboard.writeText(shareData.url);&lt;br&gt;
  showMessage("Link copied");&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;The feature should still work when &lt;code&gt;navigator.share&lt;/code&gt; is unavailable.&lt;/p&gt;

&lt;p&gt;A good fallback order is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the native share sheet when supported.&lt;/li&gt;
&lt;li&gt;Copy the public URL to the clipboard.&lt;/li&gt;
&lt;li&gt;Display the URL if clipboard access fails.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;typescript&lt;br&gt;
async function copyShareUrl(url: string): Promise {&lt;br&gt;
  try {&lt;br&gt;
    await navigator.clipboard.writeText(url);&lt;br&gt;
    return true;&lt;br&gt;
  } catch {&lt;br&gt;
    return false;&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Copying a link is not as visually impressive as opening a native menu, but reliability matters more than novelty.&lt;/p&gt;

&lt;p&gt;The interface should also confirm what happened. A silent copy action leaves visitors unsure whether the button worked.&lt;/p&gt;

&lt;p&gt;Social previews need server-visible metadata&lt;/p&gt;

&lt;p&gt;A single-page application can update its title and description after JavaScript loads:&lt;/p&gt;

&lt;p&gt;javascript&lt;br&gt;
document.title = "A Gentle Turning Point — 恋みくじ";&lt;/p&gt;

&lt;p&gt;That is helpful for the visitor, but many social preview crawlers do not run client-side JavaScript in the same way as a browser.&lt;/p&gt;

&lt;p&gt;If the shared URL needs a specific preview image and description, those tags should appear in the initial HTML response.&lt;/p&gt;

&lt;p&gt;html&lt;br&gt;

  property="og:title"&lt;br&gt;
  content="A Gentle Turning Point — 恋みくじ"&lt;br&gt;
/&amp;gt;&lt;/p&gt;

&lt;p&gt;
  property="og:description"&lt;br&gt;
  content="A small change in perspective may reveal a new path."&lt;br&gt;
/&amp;gt;&lt;/p&gt;

&lt;p&gt;
  property="og:image"&lt;br&gt;
  content="https://example.com/share/gentle-turning-point-014.png"&lt;br&gt;
/&amp;gt;&lt;/p&gt;

&lt;p&gt;
  property="og:url"&lt;br&gt;
  content="https://example.com/fortune/en/gentle-turning-point-014"&lt;br&gt;
/&amp;gt;&lt;/p&gt;

&lt;p&gt;
  name="twitter:card"&lt;br&gt;
  content="summary_large_image"&lt;br&gt;
/&amp;gt;&lt;/p&gt;

&lt;p&gt;There are two practical ways to generate this HTML.&lt;/p&gt;

&lt;p&gt;Static generation&lt;/p&gt;

&lt;p&gt;If the result catalog is finite, the application can create one static share route for every published result and language.&lt;/p&gt;

&lt;p&gt;typescript&lt;br&gt;
for (const result of publishedResults) {&lt;br&gt;
  for (const locale of supportedLocales) {&lt;br&gt;
    generateResultPage(result, locale);&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Advantages include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fast delivery&lt;/li&gt;
&lt;li&gt;Easy caching&lt;/li&gt;
&lt;li&gt;No database query during sharing&lt;/li&gt;
&lt;li&gt;Predictable metadata&lt;/li&gt;
&lt;li&gt;Low operational complexity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Server rendering&lt;/p&gt;

&lt;p&gt;A server handler can look up the result ID and render the metadata dynamically.&lt;/p&gt;

&lt;p&gt;typescript&lt;br&gt;
export async function renderSharePage(&lt;br&gt;
  resultId: string,&lt;br&gt;
  locale: string&lt;br&gt;
) {&lt;br&gt;
  const result = await getPublishedResult(resultId, locale);&lt;/p&gt;

&lt;p&gt;if (!result) {&lt;br&gt;
    return renderNotFound();&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;return renderHtml({&lt;br&gt;
    title: result.headline,&lt;br&gt;
    description: result.shortInterpretation,&lt;br&gt;
    image: result.shareImage&lt;br&gt;
  });&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;This is more flexible, but it introduces additional caching, validation, and availability concerns.&lt;/p&gt;

&lt;p&gt;For a relatively stable result catalog, static generation is often the simpler choice.&lt;/p&gt;

&lt;p&gt;Generate images from trusted content&lt;/p&gt;

&lt;p&gt;Visual result cards are attractive because they can preserve the atmosphere of the experience when shared outside the website.&lt;/p&gt;

&lt;p&gt;However, image generation can become a security problem if arbitrary user input is inserted into a template.&lt;/p&gt;

&lt;p&gt;A safer renderer accepts only trusted catalog content:&lt;/p&gt;

&lt;p&gt;typescript&lt;br&gt;
type ShareCardInput = {&lt;br&gt;
  resultId: string;&lt;br&gt;
  locale: string;&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;The server retrieves the published text itself.&lt;/p&gt;

&lt;p&gt;It should not accept a complete headline from the request:&lt;/p&gt;

&lt;p&gt;typescript&lt;br&gt;
// Avoid this design&lt;br&gt;
type UnsafeShareCardInput = {&lt;br&gt;
  customHeadline: string;&lt;br&gt;
  customMessage: string;&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;Allowing arbitrary text can lead to impersonation, offensive content, layout abuse, resource exhaustion, or misleading cards carrying the product’s branding.&lt;/p&gt;

&lt;p&gt;If custom text is not essential, do not support it.&lt;/p&gt;

&lt;p&gt;The generated images can be cached using stable paths:&lt;/p&gt;

&lt;p&gt;text&lt;br&gt;
/share-cards/en/gentle-patience-014-v2.png&lt;/p&gt;

&lt;p&gt;Including a content version makes cache invalidation explicit.&lt;/p&gt;

&lt;p&gt;Saving is different from sharing&lt;/p&gt;

&lt;p&gt;Some visitors may want to keep a result without publishing it anywhere.&lt;/p&gt;

&lt;p&gt;The application can offer a local download:&lt;/p&gt;

&lt;p&gt;typescript&lt;br&gt;
async function downloadResultCard(&lt;br&gt;
  imageBlob: Blob,&lt;br&gt;
  filename: string&lt;br&gt;
) {&lt;br&gt;
  const url = URL.createObjectURL(imageBlob);&lt;br&gt;
  const anchor = document.createElement("a");&lt;/p&gt;

&lt;p&gt;anchor.href = url;&lt;br&gt;
  anchor.download = filename;&lt;br&gt;
  anchor.click();&lt;/p&gt;

&lt;p&gt;URL.revokeObjectURL(url);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;This gives the visitor control over where the file goes.&lt;/p&gt;

&lt;p&gt;The application does not need to upload the image to a user account first. It also does not need access to the visitor’s photo library or cloud storage.&lt;/p&gt;

&lt;p&gt;“Save” should mean exactly what the interface claims. It should not silently create a public page.&lt;/p&gt;

&lt;p&gt;Avoid creating thousands of thin indexable pages&lt;/p&gt;

&lt;p&gt;Share routes introduce an SEO question.&lt;/p&gt;

&lt;p&gt;If every result has a public URL, it may be tempting to make every result page indexable. A large collection of pages containing only a headline and two short paragraphs, however, may provide little independent search value.&lt;/p&gt;

&lt;p&gt;A better strategy is to separate shareability from indexability.&lt;/p&gt;

&lt;p&gt;The main product page can explain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What 恋みくじ means&lt;/li&gt;
&lt;li&gt;How the experience works&lt;/li&gt;
&lt;li&gt;Its cultural background&lt;/li&gt;
&lt;li&gt;Privacy expectations&lt;/li&gt;
&lt;li&gt;Frequently asked questions&lt;/li&gt;
&lt;li&gt;The difference between entertainment and advice&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Individual result routes can remain accessible for sharing without being treated as primary search landing pages.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;html&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;This tells search engines that the page may be followed but should not be included as an individual search result.&lt;/p&gt;

&lt;p&gt;Not every public URL needs to become an SEO page.&lt;/p&gt;

&lt;p&gt;Indexing should be based on lasting value, not the number of routes an application can generate.&lt;/p&gt;

&lt;p&gt;Keep analytics away from private meaning&lt;/p&gt;

&lt;p&gt;A useful sharing event might look like this:&lt;/p&gt;

&lt;p&gt;typescript&lt;br&gt;
analytics.track("fortune_share_started", {&lt;br&gt;
  locale: currentLocale,&lt;br&gt;
  method: "web_share"&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;Before attaching more information, ask why it is necessary.&lt;/p&gt;

&lt;p&gt;Does the analytics platform need the exact result ID?&lt;/p&gt;

&lt;p&gt;Does it need the situation selected before the draw?&lt;/p&gt;

&lt;p&gt;Does it need a persistent identifier connecting this draw to future visits?&lt;/p&gt;

&lt;p&gt;In many cases, aggregate counts are sufficient:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Share button opened&lt;/li&gt;
&lt;li&gt;Link successfully copied&lt;/li&gt;
&lt;li&gt;Download completed&lt;/li&gt;
&lt;li&gt;Share operation cancelled&lt;/li&gt;
&lt;li&gt;Error occurred&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the product needs result-level statistics, the result ID is public catalog information. Even then, it should not be connected to personal questions or persistent visitor profiles without a clear reason and appropriate consent.&lt;/p&gt;

&lt;p&gt;Handle cancellation as a normal outcome&lt;/p&gt;

&lt;p&gt;Sharing is user-controlled. Closing the native share sheet is not an application error.&lt;/p&gt;

&lt;p&gt;typescript&lt;br&gt;
async function handleShare(&lt;br&gt;
  shareData: ShareableFortune&lt;br&gt;
) {&lt;br&gt;
  try {&lt;br&gt;
    await navigator.share(shareData);&lt;br&gt;
  } catch (error) {&lt;br&gt;
    if (error instanceof DOMException &amp;amp;&amp;amp;&lt;br&gt;
        error.name === "AbortError") {&lt;br&gt;
      return;&lt;br&gt;
    }&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;reportTechnicalError(error);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;}&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Do not show an alarming message when the visitor simply decides not to share.&lt;/p&gt;

&lt;p&gt;The application should also avoid immediately asking again. A cancelled share action should end quietly.&lt;/p&gt;

&lt;p&gt;Accessibility remains part of sharing&lt;/p&gt;

&lt;p&gt;A visual card should not be the only representation of the result.&lt;/p&gt;

&lt;p&gt;The shared page needs real text so that it can be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read by screen readers&lt;/li&gt;
&lt;li&gt;Translated&lt;/li&gt;
&lt;li&gt;Copied&lt;/li&gt;
&lt;li&gt;Enlarged&lt;/li&gt;
&lt;li&gt;Displayed when the image fails&lt;/li&gt;
&lt;li&gt;Understood without relying on color&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Share and copy buttons need descriptive labels:&lt;/p&gt;

&lt;p&gt;html&lt;br&gt;
&lt;br&gt;
  Share result&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;Status messages should be announced without moving focus unexpectedly:&lt;/p&gt;

&lt;p&gt;html&lt;/p&gt;

&lt;p&gt;
  Link copied
&lt;/p&gt;

&lt;p&gt;A beautiful image is an enhancement. It should not become the only accessible source of information.&lt;/p&gt;

&lt;p&gt;Applying the idea to a real project&lt;/p&gt;

&lt;p&gt;I have been considering these decisions while working with &lt;a href="https://www.ichizenn.com/koi-mikuji/" rel="noopener noreferrer"&gt;Ichizenn’s 恋みくじ&lt;/a&gt;, a browser-based Japanese love-fortune experience.&lt;/p&gt;

&lt;p&gt;The key architectural lesson is that personalization does not always require identification.&lt;/p&gt;

&lt;p&gt;A result can feel relevant because of timing, context, writing, and interaction design. The application does not need to know who the visitor is or store the private thought that brought them there.&lt;/p&gt;

&lt;p&gt;This changes the sharing model completely.&lt;/p&gt;

&lt;p&gt;Instead of sharing a user record, the application shares a reference to published content.&lt;/p&gt;

&lt;p&gt;The resulting architecture&lt;/p&gt;

&lt;p&gt;The complete flow can remain relatively small:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The visitor privately considers a romantic question.&lt;/li&gt;
&lt;li&gt;The browser selects a suitable published result.&lt;/li&gt;
&lt;li&gt;The application displays that result without requiring an account.&lt;/li&gt;
&lt;li&gt;The share object contains only a public result ID and locale.&lt;/li&gt;
&lt;li&gt;The public route renders server-visible metadata.&lt;/li&gt;
&lt;li&gt;The visitor chooses whether to share, copy, download, or leave.&lt;/li&gt;
&lt;li&gt;No personal question is included in the shared artifact.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The system can still provide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Native mobile sharing&lt;/li&gt;
&lt;li&gt;Attractive social previews&lt;/li&gt;
&lt;li&gt;Downloadable result cards&lt;/li&gt;
&lt;li&gt;Multiple languages&lt;/li&gt;
&lt;li&gt;Stable public links&lt;/li&gt;
&lt;li&gt;Basic aggregate analytics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It does not need a profile database to accomplish any of these things.&lt;/p&gt;

&lt;p&gt;Final thoughts&lt;/p&gt;

&lt;p&gt;Privacy-friendly architecture is often described as a limitation.&lt;/p&gt;

&lt;p&gt;In this case, it can make the product simpler.&lt;/p&gt;

&lt;p&gt;By refusing to store information that the sharing feature does not need, we reduce:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database complexity&lt;/li&gt;
&lt;li&gt;Access-control requirements&lt;/li&gt;
&lt;li&gt;Potential data leaks&lt;/li&gt;
&lt;li&gt;Account recovery flows&lt;/li&gt;
&lt;li&gt;Moderation problems&lt;/li&gt;
&lt;li&gt;User anxiety about personal questions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most important design rule is also the simplest:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Share the fortune, not the person behind it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For products involving emotions, reflection, health, identity, or personal decisions, what information could remain entirely inside the visitor’s browser?&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>privacy</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
