Exploring the 'About This Website' Page

last updated by ewan datetime loading...

(datetime loading...)

View on WhiteWind or see the record at atproto.at

5 min read • 878 words


My website includes an 'About This Website' page, which provides details about its purpose, technology stack, privacy notice, and more. This page was recently converted from static hardcoded content to a dynamic system that fetches data from my Personal Data Server (PDS) via the AT Protocol.

The Conversion Process

Originally, the About page contained all its information hardcoded directly in the Svelte component. This meant that any updates to the website's information required code changes and redeployment. To make content management more flexible and centralised, I converted the page to dynamically fetch its content from my PDS.

The conversion involved:

  • Moving all static content from the Svelte template to a structured data format on my PDS
  • Implementing the getSiteInfo function to fetch this data via the AT Protocol
  • Updating the Svelte component to render the fetched data instead of static content
  • Maintaining backward compatibility by showing a loading message when data is not available

How the Information is Structured

The core information displayed on the page is now defined by a Lexicon, specifically the uk.ewancroft.site.info Lexicon. This Lexicon outlines the structure for various pieces of site information, such as:

  • technologyStack: A list of technologies used.
  • privacyStatement: Details on privacy and data handling.
  • openSourceInfo: Information about the project's open-source nature, including licences, repositories, and related services.
  • credits: Acknowledgements for assets, libraries, etc.
  • additionalInfo: A flexible section for things like website purpose, contact details, analytics, and deployment information.

Fetching the Data

The data conforming to this Lexicon is stored on my PDS. The website fetches this information using the getSiteInfo function. This function makes an authenticated request to the PDS to retrieve the record associated with the uk.ewancroft.site.info collection and the self rkey.

/**
 * Fetches site information from the user's PDS.
 * @returns A Promise that resolves to SiteInfo or null if not found or an error occurs.
 */
export async function getSiteInfo(): Promise<SiteInfo | null> {
  try {
    const profile: Profile = await getProfile(); // Assuming getProfile is available and returns the user's profile with PDS and DID
    const rawResponse = await fetch(
      `${profile.pds}/xrpc/com.atproto.repo.listRecords?repo=${profile.did}&collection=uk.ewancroft.site.info&rkey=self`
    );
    const response = await rawResponse.json();

    if (response && response.records && response.records.length > 0) {
      // Assuming the record structure matches the SiteInfo interface
      return response.records[0].value as SiteInfo;
    } else {
      console.log("No site info record found.");
      return null;
    }
  } catch (error) {
    console.error("Error fetching site info:", error);
    return null;
  }
}

Displaying the Information

The fetched siteInfo data is then used by the Svelte page component to render the content. The component iterates through the various sections defined in the SiteInfo type (which is derived from the Lexicon) and displays the relevant details. The component now includes conditional rendering to handle cases where the PDS data might not be available, falling back to a loading message.

<script lang="ts">
  import { page } from "$app/stores";
  import type { SiteInfo } from "$lib/components/profile/profile";

  // Access the siteInfo data from the layout load function
  const siteInfo: SiteInfo | null = $page.data.siteInfo;
</script>

// ... existing code ...

{#if siteInfo}
    <div class="prose dark:prose-invert">
      {#if siteInfo.additionalInfo?.purpose}
        <h2 class="text-xl font-semibold mt-6 mb-2">Website Purpose</h2>
        <p>{@html siteInfo.additionalInfo.purpose}</p>
      {/if}

      {#if siteInfo.technologyStack && siteInfo.technologyStack.length > 0}
        <h2 class="text-xl font-semibold mt-6 mb-2">Technology Stack</h2>
        <p>This website is built with:</p>
        <ul>
          {#each siteInfo.technologyStack as tech}
            <li>
              {#if tech.url}
                <a
                  href="{tech.url}"
                  class="text-[var(--link-color)] hover:text-[var(--link-hover-color)]"
                  >{tech.name}</a
                >
              {:else}
                {tech.name}
              {/if}
              {#if tech.description}
                - {@html tech.description}
              {/if}
            </li>
          {/each}
        </ul>
      {/if}

      {#if siteInfo.privacyStatement}
        <h2 class="text-xl font-semibold mt-6 mb-2">Privacy Notice</h2>
        <p>{@html siteInfo.privacyStatement}</p>
      {/if}

// ... existing code ...

      {#if siteInfo.credits && siteInfo.credits.length > 0}
        <h2 class="text-xl font-semibold mt-6 mb-2">Credits</h2>
        <ul>
          {#each siteInfo.credits as credit}
            <li>
              {#if credit.url}
                <a
                  href="{credit.url}"
                  class="text-[var(--link-color)] hover:text-[var(--link-hover-color)]"
                  >{credit.name}</a
                >
              {:else}
                {credit.name}
              {/if}
              {#if credit.type}
                ({credit.type})
              {/if}
              {#if credit.author}
                by {credit.author}
              {/if}
              {#if credit.licence}
                under {#if credit.licence.url}
                  <a
                    href="{credit.licence.url}"
                    class="text-[var(--link-color)] hover:text-[var(--link-hover-color)]"
                    >{credit.licence.name || 'Licence'}</a
                  >
                {:else}
                  {credit.licence.name || 'Licence'}
                {/if}
              {/if}
              {#if credit.description}
                - {@html credit.description}
              {/if}
            </li>
          {/each}
        </ul>
      {/if}

// ... existing code ...

        {#if siteInfo.additionalInfo}
        {#if siteInfo.additionalInfo.contact}
          <h2 class="text-xl font-semibold mt-6 mb-2">Contact</h2>
          {#if siteInfo.additionalInfo.contact.email}
            <p>Email: <a href="mailto:{siteInfo.additionalInfo.contact.email}" class="text-[var(--link-color)] hover:text-[var(--link-hover-color)]">{siteInfo.additionalInfo.contact.email}</a></p>
          {/if}
          {#if siteInfo.additionalInfo.contact.social && siteInfo.additionalInfo.contact.social.length > 0}
            <p>Social:</p>
            <ul>
              {#each siteInfo.additionalInfo.contact.social as social}
                <li>
                  {#if social.url}
                    <a
                      href="{social.url}"
                      class="text-[var(--link-color)] hover:text-[var(--link-hover-color)]"
                      >{social.platform}</a
                    >
                  {:else}
                    {social.platform}
                  {/if}
                  {#if social.handle}
                    ({social.handle})
                  {/if}
                </li>
              {/each}
            </ul>
          {/if}
        {/if}

        {#if siteInfo.additionalInfo.analytics}
          <h2 class="text-xl font-semibold mt-6 mb-2">Analytics</h2>
          {#if siteInfo.additionalInfo.analytics.services && siteInfo.additionalInfo.analytics.services.length > 0}
            <p>Services: {siteInfo.additionalInfo.analytics.services.join(', ')}</p>
          {/if}
          {#if siteInfo.additionalInfo.analytics.cookiePolicy}
            <p>Cookie Policy: {@html siteInfo.additionalInfo.analytics.cookiePolicy}</p>
          {/if}
        {/if}

        {#if siteInfo.additionalInfo.deployment}
          <h2 class="text-xl font-semibold mt-6 mb-2">Deployment</h2>
          {#if siteInfo.additionalInfo.deployment.platform}
            <p>Platform: {siteInfo.additionalInfo.deployment.platform}</p>
          {/if}
          {#if siteInfo.additionalInfo.deployment.cdn}
            <p>CDN: {siteInfo.additionalInfo.deployment.cdn}</p>
          {/if}
          {#if siteInfo.additionalInfo.deployment.customDomain !== undefined}
            <p>Custom Domain: {siteInfo.additionalInfo.deployment.customDomain ? 'Yes' : 'No'}</p>
          {/if}
        {/if}
      {/if}
    </div>
  {:else}
    <p>Loading site information...</p>
  {/if}
</div>

Benefits of the Conversion

This setup allows me to manage the website's descriptive information centrally on my PDS, keeping the website code focused on presentation rather than content management. The key benefits include:

  • Centralised Content Management: All site information is stored in one place on my PDS
  • No Code Changes Required: Updates to site information no longer require code changes and redeployment
  • Consistent Structure: The Lexicon ensures all site information follows a consistent format
  • Decentralised Architecture: Leverages the AT Protocol for data storage and retrieval
  • Maintainability: Separates content from presentation logic, making both easier to maintain