sample code in the Svelte language

Data Fetching in SvelteKit vs. Next.js

Unsure how to fetch external data in SvelteKit? Replacing the Next.js data fetching methods with SvelteKit endpoints is quite simple.

Back to blog

SvelteKit: a unified approach to data fetching

With Next.js, data fetching methods and API routes exist separately. SvelteKit takes a more unified approach with endpoints. Endpoints can be either standalone or page-specific.

In Next.js terms, API routes are replaced by standalone endpoints and per-page data fetching methods like getStaticProps and getServerSideProps are substituted with page endpoints.

Like in Next.js, SvelteKit endpoints go in the same folder as routes (pages in Next.js and src/routes in SvelteKit). They can be in .js, .ts, or .json format.

Page endpoints live in the same folder as their page counterparts and share the same names (index.js pairs with index.svelte, for example). Standalone endpoints have unique names and are usually kept in a separate sub-directory (I like to keep mine in src/routes/api).

routes directory example in SvelteKit

Getting started

If you haven’t already, create a SvelteKit project with the following command:

npm init svelte my-app

cd into the app directory, install the dependencies using your package manager of choice, and ensure there’s an index.svelte file in src/routes.

The src/routes folder acts like the pages folder in Next.js. Any .svelte file placed in here becomes a page on your website (with the exception of some special pages like __layout.svelte and __error.svelte).

Now that your project is all set up, we’ll walk through how to get the same functionality of Next’s getStaticProps, getStaticPaths, getServerSideProps and API routes in SvelteKit.

getStaticProps & getServerSideProps

Next.js

In Next.js, fetching data looks something like this:

// pages/index.jsx

const HomePage = ({ page }) => {
    return (
        <section>
            <h1>{page.title}</h1>
            <p>{page.content}</p>
        </section>
    )
}

export const getStaticProps = async () => {
    const page = await (await fetch('https://your-cms.com/api')).json()

    return {
        props: {
            page
        }
    }
}

export default HomePage

SvelteKit

To create the equivalent in SvelteKit, go to your SvelteKit app and create an index.js file in the src/routes directory.

// src/routes/index.js

export const get = async () => {
    const page = await (
        await fetch(
            'https://your-cms.com/api'
        )
    ).json()

    return {
        status: 200,
    // `page` becomes a prop available 
    // in the companion `.svelte` file
        body: { page }
    }
}

All data returned in the body of the get function will be available as props to the index.svelte page.

// src/routes/index.svelte

<script>
  // This variable is made available
  // in the `body` object returned from `index.ts`
  export let page
</script>

<section>
    <h1>{page.title}</h1>
    <p>{page.content}</p>
</section>

This isn't so different from Next! SvelteKit separates front-end from back-end (server-side) logic, which is a plus in our view.

SvelteKit is SSR (server-side rendered) by default. A major difference between SSG and SSR in SvelteKit is that endpoints run on every request for SSR, and only at build-time for SSG. To prerender a single page, export prerender = true from the page’s script tag:

<script>
    export const prerender = true
</script>

To prerender the entire app, add config.kit.prerender.default = true to your svelte.config.js file.

// svelte.config.js

const config = {
  kit: {
    prerender: {
      default: true
    }
  }
}

getStaticPaths and dynamic routes

Next.js

In static Next.js sites, getStaticPaths is used to generate dynamic routes at build-time. For SSR sites, dynamic routing is handled in getServerSideProps.

// pages/users/[uid].jsx

// SSR
export const getServerSideProps = async ({ params }) => {
    const user = await (await fetch(`https://your-cms.com/api/users/${params.uid}`)).json()

  if (!user) {
    return {
      notFound: true
    }
  }

    return {
        props: {
            user
        }
    }
}
// pages/posts/[id].jsx

// SSG
export const getStaticProps = async ({ params }) => {
    const post = await (await fetch(`https://your-cms.com/api/posts/${params.id}`)).json()

    return {
        props: {
            post
        }
    }
}

export const getStaticPaths = async () => {
    const posts = await (await fetch('https://your-cms.com/api/posts')).json()

    const paths = posts.reduce((arr, post) => {
        if (post?.id) {
            arr.push({ params: { id: post.id } })
        }

        return arr
    }, [])

    return {
        paths,
        fallback: false
    }
}

SvelteKit

Once again, both static and server-rendered approaches are consolidated into the same endpoint function. In your SvelteKit project, create the folder src/routes/post. In this folder, create two files: [id].svelte and [id].js.

// src/routes/[uid].js

export const get = async ({ params }) => {
    const post = await (
        await fetch(
            `https://your-cms.com/api/posts/${params.id}`
        )
    ).json()

  if (!post) {
        return {
      status: 404
    }
  }

    return {
        status: 200,
        body: { post }
    }
}

When using SSR, SvelteKit handles all the rest! When [params.id](http://params.id) doesn’t match a post in your database or CMS, the page returns a 404. Otherwise, the post prop is passed to your /post/[uid] page.

<script>
  export let post
</script>

<article>
  <h1>{post.title}</h1>
  <p>{post.content}</p>
</article>

SvelteKit will only prerender a static page if it can be reached from the home page (i.e. it exists in the link tree of the website). If you want to explicitly define what pages to prerender at build time, use the [config.kit.prerender.entries option](https://kit.svelte.dev/docs/configuration#prerender) in svelte.config.js.

API routes

If you need to send HTTP requests to an API route on the client side, use a standalone endpoint.

In endpoints, you can access each HTTP request type by creating a function with the same name as the method you want to target. For instance, if you want to POST to an endpoint, export an async function called post.

By explicitly separating HTTP requests, SvelteKit’s endpoints more closely resemble popular server frameworks like Express and Fastify than Next’s API routes.

SvelteKit

Create a folder src/routes/api. Add a file named example.js to it. Enable that endpoint to handle POST and GET requests by adding the following:

export const get = async ({ url }) => {
    console.log(url.searchParams)

    return { status: 200 }
}

export const post = async ({ request }) => {
    const body = await request.json()
    console.log(body)

    return { status: 200 }
}

Now, you can make requests to that endpoint from the client! For instance, in src/routes/index.svelte, you can add the following:

<button
    on:click={async () => {
        await fetch('/api/example?test=true')

        await fetch('/api/example', {
            method: 'POST',
            body: JSON.stringify({ test: true })
        })
    }}
>
    Click Here to Fetch
</button>

In your server console, you should see this:

URLSearchParams { 'test' => 'true' }
{ test: true }

Standalone endpoints are useful for handling logic decoupled from a specific route, such as checking out a cart on an eCommerce site. They can also be used to handle API calls from external services, like webhooks.