NNicheStackTutorials
← All tutorials
SvelteKit

SvelteKit Local JSON Data: No Backend, No CMS, No Regrets

Load blog posts and tool configs from JSON files at build time using import.meta.glob. Includes TypeScript types and hot reload.

·10 min read·SvelteKit
SvelteKit Local JSON Data: No Backend, No CMS, No Regrets cover

Tested on: SvelteKit 2.16, TypeScript 5.7 Use case: 40 blog posts, 12 tool configs. No headless CMS budget.

Folder layout

src/
├── lib/data/
│   ├── posts.json
│   ├── tools.json
│   └── types.ts
└── routes/
    ├── blog/+page.ts
    └── blog/[slug]/+page.ts

types.ts

export interface Post {
  slug: string;
  title: string;
  date: string;
  tags: string[];
  excerpt: string;
}

export interface PostDetail extends Post {
  content: string;
}

posts.json (snippet)

[
  {
    "slug": "first-post",
    "title": "First Post",
    "date": "2026-05-01",
    "tags": ["sveltekit"],
    "excerpt": "Short summary.",
    "content": "# Hello\n\nBody here."
  }
]

Load at build time — blog index

src/routes/blog/+page.ts:

import type { PageLoad } from './$types';
import type { Post } from '$lib/data/types';
import posts from '$lib/data/posts.json';

export const prerender = true;

export const load: PageLoad = () => {
  const sorted = (posts as Post[]).sort(
    (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()
  );
  return { posts: sorted };
};

Dynamic slug route

src/routes/blog/[slug]/+page.ts:

import type { PageLoad } from './$types';
import type { PostDetail } from '$lib/data/types';
import posts from '$lib/data/posts.json';

export const prerender = true;

export const load: PageLoad = ({ params }) => {
  const post = (posts as PostDetail[]).find((p) => p.slug === params.slug);
  if (!post) throw error(404, 'Post not found');
  return { post };
};

export function entries() {
  return (posts as PostDetail[]).map((p) => ({ slug: p.slug }));
}

Alternative: glob many small JSON files

For one-file-per-post:

const modules = import.meta.glob('/src/content/posts/*.json', { eager: true });
const posts = Object.values(modules).map((m: any) => m.default);

Error — JSON import type complaints

Module '$lib/data/posts.json' cannot be imported because
it is not a valid ECMAScript module

tsconfig.json:

{
  "compilerOptions": {
    "resolveJsonModule": true,
    "esModuleInterop": true
  }
}

Hot reload works

Edit posts.json, save — Vite HMR refreshes the page in dev. No API restart.

When this breaks down

Past ~200 posts, consider MDX or a CMS. JSON in git gets noisy in diffs. For small sites, it's perfect.

Build time with 40 posts: 8s
Zero API calls at runtime
Zero hosting cost for data layer

Tags: SvelteKitJSONStatic Dataimport.meta.glob