SvelteModularHomepage Svelte Themes

Sveltemodularhomepage

SvelteModularHomepage

A configurable portal for client specific collections of virtual events, with integral cloud storage for images and entirely dynamic content with language support capabilities.

This project is intended to run alongside SvelteModularHomepageAdmin.

One admin instance serves all portal instances in a given data environment.

The database schema and local container instances are maintained in this project, so you must have this project running before starting the admin tool.

Key stack components

  • TypeScript (type safety language enhancements)
  • Vercel (CI/CD, hosting, deployment management)
  • Supabase (database, authentication, client side data library)
  • SvelteKit (application framework)
  • Tailwind (functional CSS library)
  • Flowbite (UI CSS library, functional Svelte componenents, JS UI functionality)

Installation

With the exception of Docker, installation follows the standard NPM pattern. If you are not set up for that, start here.

[!IMPORTANT] Docker Desktop must be installed and running before starting project installation.

Clone this repository, open a terminal in the new directory, and run:

npm install

[!WARNING] Do not run npm audit fix unless you are familar with the process and are certain you want the specified packages to be updated.

Checkout the dev branch to make sure you get the latest database schema.

Now initialize and start the database environments by running:

npx supabase init
npx supabase start

[!NOTE] The first run will take a while, as the Supabase Docker containers must be downloaded.

You should see the loading process, and then output that includes links to things like the web based DB admin and your anon key.

Now set your environment variables in project_root/.env.

Example:

# PORTAL (CLIENT) CONFIGURATION
PUBLIC_WHICH_ENV = "LOCAL"
PUBLIC_CLIENT = "1"
PUBLIC_DEFAULT_LOCALE = "1"
PUBLIC_DEFAULT_LOCALE_CODE = "en-US"

# LOCAL DATABASE
PUBLIC_SUPABASE_URL = "http://localhost:54321"
PUBLIC_SUPABASE_ANON_KEY = "GET THIS FROM SUPABASE START OUTPUT"

# AUTH
SUPABASE_AUTH_EXTERNAL_AZURE_SECRET="GET THIS FROM ANOTHER DEV"

Now that the environment is configured and the database is ready, you can start the application by running:

npm run dev

Successful output will include the URL for your local instance.

Supabase CLI guide

Environments

This project is designed to move through a workflow of three enviroments. It is critical to maintain this pattern, not just for code integritiy, but also because database schema migrations and other CI/CD actions depend on it.

These environments each represent a reserved branch in this repository, a seperate Vercel deployment, and a separate Supabase instance.

Production

The final destination. This environment is published from the main branch. All code pushed to this branch must first have been tested and QA'd in the Staging environment. Every push to this branch should be reflected by an update to the version in package.json and a corresponding entry in the changelog.

Staging

This environment is published from the staging branch. All code pushed to this branch should first have already been sanity checked on the dev branch and then merged into staging by a Pull Request. A pull request is required, otherwise Git actions will not execute and the publication pipeline may break.

You should also always want at least a little code review before pushing things upstream towards production.

Local

This environment is where you will actually work. Docker is required to run the various Supabase containers. You should always work on a feature branch (ideally linked to a Jira ticket). Your new branch should originate from the dev branch, and when you are done with work, should be merged back into the dev branch for validation before merging up to staging.

Local data is intentially entirely isolated from live data environments. While there is a way to link to a cloud hosted Supabase instance, keeping it entirely decoupled and strictly using migrations for any schema changes is the safest approach.

Local data is intended to be volatile and you should reset after any new migrations to test them.

Any data that is required for basic functionality should be added to seed.sql.

It may be useful at some points to export live staging or even production data, and import it into your local environment. This will allow you to do testing or debugging against more realistic data while maintaining total safety.

Dependencies

  • svelte: ^4.0.5
  • sveltejs/kit: ^1.20.4
  • supabase: ^1.99.5
  • supabase/supabase-js: ^2.38.0
  • tailwindcss: ^3.3.2
  • flowbite: ^1.8.1
  • flowbite-svelte": ^0.44.18

Development

This project is a fairly simple and standard SvelteKit build, with a few special considerations, covered here.

Themes

Custom styles for individual clients.

Brand colors

[!NOTE] todo

Custom fonts

Installation

Compatible font files must be copied into the fonts folder and manually added as an availble font face in the main application stylesheet.

[!IMPORTANT] Do not include the /static prefix in the fonts URL. Files in that folder become available at the root folder after build.

Example:

@font-face {
  font-family: Inter;
  src: url(/fonts/Inter-Regular.ttf);
}
Usage

There are two configurable fonts (fontBody, fontHeader) in the client theme JSON object, and they are assigned to corresponding CSS variables (--theme-body-font, --theme-header-font). In general you will not need to use the CSS vars explicitly.

By default, the client body font will be used for all text content except for sections overridden with the client header font.

As it stands, you must wrap text sections that a client would consider a "header" in the <HeaderFont> component. This component will only apply the selected font, all other styles (such as font size) should be handled in the containing element, and that element should be an H1(2,3,etc) tag based on it's placement in the document structure.

Content

This system handles all user facing content dynamically, and renders each content item with a best effort to provide an accurate localized user experience.

Images

Image content, such as client logos and event promo photos, are stored using Supabase storage.

Image buckets are stored as database rows, and new buckets must be added to a migration manually (or via DB admin on target environments).

Image content will not upload on migrations, and must be added manually in target environments.

In the future, the image URL mapping should be overloaded to accomodate optional localized (region specific) images.

Template (page) content

All template content should be rendered with the localization system. The content dictionary is populated at runtime and available for atypical use cases in the $dictionary store found in application.ts.

Content management is handled in the admin, and it is recommended that you run an instance of the admin repository locally and use the tools there to maintain data integrity.

In a pinch, you can also add content items through your locally hosted Supabase Studio (the link is available on terminal output when starting the Supabase containers).

Localized page content items are referred to by a "slug", an arbitrary text based index. It is recommended to use the Page property on the content item to group content associated with a shm,nbm pecific route/view/screen whenever possible. Content items must be associated to a locale (language) and can optionally be associated with a client, for custom content overrides on any UI elements.

[!IMPORTANT] You must not allow truly duplicate (matching slug, locale, and client) local_content items.

When developing, most written content will be rendered with the LocalText component.

Usage:

<h1>
    <LocalText slug="hero_title" />
</h1>

Occasionally you will need to render content in places that a component tag will not work, like in a text attribute. In those cases, you will use the localText function from localize.ts.

Usage:

<script lang="ts">
    import { dictionary } from "$lib/data/application";
    import { localText } from "$lib/data/localize";
</script>

<NavDropdown title={localText($dictionary, 'my_events')} icon="calendar">
    ...
</NavDropdown>
Long term considerations

For future projects, or even future versions of this project, we may want to consider using a commercial language product. For example, Inlang is widely supported and includes VSCode support and the ability to create custom plugins.

At this time, the technical debt, learning curve, and additional work trying to make any of these systems work with our specific use case in this project make it more practical to use a custom system, but something to keep on the radar.

Alternative solutions

Meeting (event) content

Meeting content is handled in a way that is similar to page content. However, instead of slugs, content is grouped by locale (language) and a dynamic Meeting Content Type. Meeting content items are contained in the Meeting object, which must be passed into the MeetingContent component. Logic for rendering the best available localized content is handled in the MeetingContent component, so you just need to pass the name of the content type and the actual meeting object you are working with.

Usage

<MeetingContent {meeting} type="Meta" />

Managing database and policy changes

Changes to the database schema must be managed with migration scripts.

[!IMPORTANT] Enabling or disabling Realtime Subscriptions on a table will not show up in the automated diff. It should be possible to build an automatic solution as discussed here. In the meantime, you must make the change manually on all data environments. Subscription listeners will fail silently if the per-table setting is not enabled.

You can manually create a new .SQL file in the migrations directory, or you can make the changes in Supabase Studio (your local DB admin tool) and then use the built in diff tool.

In the event that you can't avoid making breaking schema changes, like adding a required foreign key to a table that already has rows in production/staging, then you will need to make a per-enviroment based strategy to backfill the required data. The easiest approach is probably to execute SQL directly on the target Supabase instances.

_A more sophisticated and robust strategy would be good. Please update this as appropriate. _

Supabase diff tool

npx supabase db diff -f <descriptive_filename?>

[!NOTE] To see any pending changes without writing a new migration script file, run the above command without the file flag and parameter.

After creating a new migration with the diff tool, you will be reminded to make sure that there are no breaking changes by resetting your local database. This is not required, but is a good practice. It is also a good test to see if any new code requires your seed data to be updated.

npx supabase db reset

Special schemas

The diff tool will only look for changes in the public schema by default. If you make any schema changes, including RLS policies, to any other schemas (for example auth or storage), you must run an additional diff step.

npx supabase db diff -s <schema_name> -f <descriptive_filename?>

Storage buckets

Supabase storage buckets are rows in a special schema, so they will not be included when running a db diff. You must manually add insert statements to a new migration script (or append it to a new one you have generated, as long as it has not already been pushed).

Alternatively, you can add the buckets manually to production and staging data environments, and then add the insert statements in your seed data. This is not preferred, but will work.

Error handling

[!NOTE] Group discussion and investigation into best error handling patterns within Svelte would be good.

Using try / catch blocks in server code is a little weird, since the catch will intercept all explicit responses (throw statements). This means you have to handle redirects again in the catch, and explicit errors also get intercepted. There is almost certainly a cleaner way to do this, but for now it is better than nothing. Svelte error boundaries may now be mature enough to use in template code and SvelteKit may have hooks of some kind that we should use instead of classic try / catch patterns.

[!IMPORTANT] Regardless of final patterns, it is very important that all errors are handled in a consistant and meaningful way, and equally important that the user always gets specific feedback.

Deployment

Deployment and web publication is primarily handled by Vercel. Schema migrations and (currently disabled) type checking scripts are handled by Github workflows.

GitHub workflows

Changelog

  • <= 0.3
    • Environment and infrastructure groundwork
    • POC functionality for key features
  • 0.4
    • MVP functionality

Top categories

Loading Svelte Themes