Search code examples
typescriptsveltesveltekit

How to Properly Read a Static File in SvelteKit Using a Server-Side Load Function?


I am working on a SvelteKit project where I want to dynamically render HTML content based on a route parameter (e.g., /2023, /2022). I have static HTML files (2012.html, 2013.html, etc.) stored in the src/assets directory, and I'm trying to read them in the +page.server.ts file using the fetch API.

folder archtecuter

What I Am Trying to Achieve

For each route like /2023 or /2022, I want to dynamically load an HTML file corresponding to the year from the server and render it on the page. For example, navigating to /2023 should fetch 2023.html from the src/assets or any folder and display its content. I just would like to avoid using static/ folder because I dont want to expose html like that and i do want users to visit proper svelte page.

My Setup

Here is my server-side load function in +page.server.ts:

import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async ({ params, fetch }) => {
  try {
    // Use fetch to get the HTML content from the server
    console.log(params.year) //works e.g."2012"
    const {year} = params
    const response = await fetch(`src/assets/${year}.html`); // what is the proper method to get file?

    // Check if the response is OK (status 200-299)
    if (!response.ok) {
      console.error(`Error fetching the HTML file: ${response.status} ${response.statusText}`);
      return { dom: { html: '' } };
    }

    // Read the response as text
    const html = await response.text();

    // Log the HTML content for debugging
    console.log('HTML content:', html);

    return {
      dom: { html },
    };
  } catch (error) {
    console.error('Error reading the HTML file:', error);
    return {
      dom: { html: '' }, // Return empty string on any error
    };
  }
};

And my corresponding Svelte page component +page.svelte looks like this:

<script lang="ts">
  import type { PageData } from './$types';

  export let data: PageData;
</script>

<pre>{JSON.stringify(data, null, 2)}</pre>

The Problem

When I try to access any of the routes, I consistently get a 500 Internal Server Error. Here are the logs I receive on each reload:

Error fetching the HTML file: 500 Internal Server Error
Error fetching the HTML file: 500 Internal Server Error
2:28:49 AM [vite] page reload src/routes/[year]/+page.server.ts
Error fetching the HTML file: 500 Internal Server Error

Solution

  • Files can be dynamically imported. For certain file types there already is a handler, e.g. images are imported as URLs to an asset, but there also are query params to instruct Vite how to import.

    Here you could just import the contents directly using ?raw like this:

    const file = await import(`../../assets/${year}.html?raw`)
        .then(m => m.default);
    

    (Not sure if aliases can be used, tried using $lib and that did not seem to work.)

    If you make the route just a standalone +server.ts endpoint (no +page.svelte), you can return the contents using text:

    // routes/[year]/+server.ts
    import { text, type RequestHandler } from '@sveltejs/kit';
    
    export const GET = (async ({ params }) => {
        const file = await import(`../../assets/${params.year}.html?raw`)
            .then(m => m.default);
    
        return text(file, {
            headers: {
                'Content-Type': 'text/html',
            },
        });
    }) satisfies RequestHandler;
    

    As pointed out by Peppe L-G, the ?raw import transforms the contents to a string in JS, which has some overhead and prevents more direct streaming to the client. An alternative might be using an ?url import in combination with read which should be equivalent to Peppe's answer.

    If you don't transform the HTML or add other route-specific logic, you might as well use the static directory instead.