Deploy Single Page Apps

Connect supports deploying static sites. While intended to support Quarto, R Markdown, and Jupyter documents rendered in a data science development environment, you can also use Connect to serve applications built with JavaScript libraries like React, Vue, or Svelte. This content focuses on an example application where server-side rendering1 is not enabled, as this requires functionality that Connect does not supply.

Familiarity with JavaScript developer tools is assumed. However, you must adapt the steps below based on your preferred library. Depending on the level of interactivity in your application, searching for the terms “static site generation”, “single-page app”, or “client-side routing” may point you to the relevant configuration settings your app will require to run on Connect.

Prerequisites

Before you begin, you must have:

  • the public address of the Connect server
  • a valid Connect Publisher or Administrator account
  • a working npm installation

Step 1: Create an example application

From a terminal session, create an example Svelte application:

npx sv create --template demo --no-add-ons --types ts svelte-connect 
cd svelte-connect
npm run dev -- --open

Install the static adapter:

npm i -D @sveltejs/adapter-static

Update the configuration to use the static adapter, and add a fallback route:

svelte.config.js
import adapter from '@sveltejs/adapter-static';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';

/** @type {import('@sveltejs/kit').Config} */
export default {
 preprocess: vitePreprocess(),

 kit: {
  adapter: adapter({
   fallback: "404.html"
  })
 }
};

Prepare a production build of the application:

npm run build

Step 2: Publish the application to Connect

Using your preferred publishing method, publish the app to Connect:

rsconnect deploy html build --entrypoint build/index.html
rsconnect::deployApp(
  appFiles = list.files(path = "build", full.names = TRUE), 
  appPrimaryDoc="build/index.html"
  )

Step 3: Obtain the app GUID

Get the unique identifier for your application, which you will need for the subsequent steps. You can find this unique identifier:

Step 4: Modify the application to run on Connect

Update the application configuration:

Configure the base path

Set the base path using the application GUID obtained in Step 3.

svelte.config.js
import adapter from '@sveltejs/adapter-static';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';

/** @type {import('@sveltejs/kit').Config} */
export default {

 preprocess: vitePreprocess(),
  kit: {
    adapter: adapter({
      fallback: "404.html"
    }),
    paths: {
      base: "/content/<STEP-3-CONTENT-GUID>",
      relative: true
    }
  }
};

If you have configured a custom content URL for your application, set that as the base path instead.

Disable server-side rendering

src/routes/+layout.js
export const ssr = false

Update application URLs to construct correct routes

Import the base path object and preprend it to all the application URLs:

src/routes/Header.svelte
<script lang="ts">
 import { page } from '$app/stores';
 import logo from '$lib/images/svelte-logo.svg';
 import github from '$lib/images/github.svg';
 import { base } from '$app/paths';
</script>

<header>
 <div class="corner">
  <a href="https://svelte.dev/docs/kit">
   <img src={logo} alt="SvelteKit" />
  </a>
 </div>

 <nav>
  <svg viewBox="0 0 2 3" aria-hidden="true">
   <path d="M0,0 L1,2 C1.5,3 1.5,3 2,3 L2,0 Z" />
  </svg>
  <ul>
   <li aria-current={$page.url.pathname === '/' ? 'page' : undefined}>
    <a href="{base}/">Home</a>
   </li>
   <li aria-current={$page.url.pathname === '/about' ? 'page' : undefined}>
    <a href="{base}/about">About</a>
   </li>
   <li aria-current={$page.url.pathname.startsWith('/sverdle') ? 'page' : undefined}>
    <a href="{base}/sverdle">Sverdle</a>
   </li>
  </ul>
  <svg viewBox="0 0 2 3" aria-hidden="true">
   <path d="M0,0 L0,3 C0.5,3 0.5,3 1,2 L2,0 Z" />
  </svg>
 </nav>

Step 5: Redeploy the application

Rebuild the application:

npm run build

Update your initial deployment:

rsconnect deploy html build --entrypoint build/index.html --app-id <STEP-3-CONTENT-GUID>
rsconnect::deployApp(
  appFiles = list.files(path = "build", full.names = TRUE), 
  appPrimaryDoc="build/index.html",
  appID="<STEP-3-CONTENT-GUID>"
  )

Considerations

If you want your single-page app to call an API served from Connect, it will require an API key. Connect API keys inherit the permissions of their creator. Consider creating a viewer account and assigning it permission to view only the APIs it is intended to access.

Footnotes

  1. See the Rendering on the Web article for a discussion of different rendering approaches: https://web.dev/articles/rendering-on-the-web↩︎