Firebase adapter for SvelteKit.
Utilise the Firebase Hosting CDN with dynamic content served by SvelteKit on Cloud Functions or Cloud Run!
✔️ SSR on Cloud Run
✔️ SSR on Cloud Functions
✔️ Integrates with existing JavaScript or TypeScript Cloud Functions!
✔️ Local production testing with Firebase Emulator
✔️ Multiple Hosting Sites
- Setup
- Configuration Overview
- Details
- Cloud Function
- Cloud Run
- Function vs Run
- Non-Goals
- FAQ
- Caveats
- Hints with Codes
- Contributing
This adapter reads firebase.json
to determine whether Cloud Functions or Cloud Run is being used and outputs the server pieces accordingly. Static assets are output to the configured dir in firebase.json:hosting.public
.
In your standard SvelteKit project:
npm install --save-dev svelte-adapter-firebase
- add adapter to
svelte.config.js
(see options in Adapter Configurations):
+import firebase from "svelte-adapter-firebase";
/** @type {import('@sveltejs/kit').Config} */
export default {
kit: {
+ adapter: firebase(),
target: "#svelte",
},
};
- in the SvelteKit project's
package.json
remove Firebase Hosting public directory beforesvelte-kit build
to work around sveltejs/kit#587
"scripts": {
"dev": "svelte-kit dev",
- "build": "svelte-kit build --verbose"
+ "build": "npx rimraf <dir used in firebase.json:hosting.public> && svelte-kit build --verbose"
}
npm run build
. Read and repeat, the output is meant as a guide!
SvelteKit is still in Beta and the Adapter API is in flux, this can result in the Adapter and SvelteKit becoming incompatible. Here is a compatibility table:
Adapter Version | SvelteKit Version |
---|---|
0.9.x |
1.0.0-next.119 |
0.8.x |
1.0.0-next.111 |
NA |
1.0.0-next.110 |
NA |
1.0.0-next.109 |
0.7.x |
1.0.0-next.107 |
0.6.x |
1.0.0-next.103 |
0.5.x |
1.0.0-next.54 |
0.4.x |
1.0.0-next.46 |
0.3.x |
1.0.0-next.27 |
Note: only the versions listed have been tested together, if others happen to work, it is just coincidence. This is beta software after all.
Adapter options:
hostingSite
- required when
firebase.json:hosting
is an array (contains many site configurations) - default: no default value
- required when
sourceRewriteMatch
- used to lookup the rewrite config to determine whether to output SSR code for Cloud Functions or Cloud Run. See Firebase Rewrite configuration docs.
- default:
**
firebaseJson
- path to your
firebase.json
file, relative from wheresvelte build
is called - default:
./firebase.json
- path to your
cloudRunBuildDir
- output dir of Cloud Run service, relative from the
firebaseJson
location - default:
./.${run.serviceId}
whererun.serviceId
is pulled from thefirebase.json
rewrite rule
- output dir of Cloud Run service, relative from the
Adapter output:
- static assets (images, CSS, Client-side JavaScript) of your SvelteKit app output to the directory defined by
firebase.json:hosting.public
- server assets (SSR JavaScript) output alongside your Cloud Functions defined by
firebase.json:functions.source
or thecloudRunBuildDir
depending on which service you are targeting infirebase.json:hosting:rewrites
Setup outlines the steps most commonly used with a single SvelteKit app. Here we go into the details of each configuration and how it interacts with the firebase.json
config.
The 3 step process is:
- select Hosting config from
firebase.json
. If more than one site, match usinghostingSite
- output static assets to the directory in the
public
field - identify the rewrite rule for SSR to determine Cloud Function or Cloud Run output. The rewrite rule is determined by a lookup of the
rewrites.source
againstsourceRewriteMatch
Due to the relaxed rules of firebase.json
we can have many valid configs. At a minimum, one or more Hosting sites is required with an associated Functions config if a Cloud Function rewrite is used. These are the combintations:
single Hosting site with Cloud Function rewrite
{
"hosting": {
"public": "<someDir>",
"rewrites": [
{
"source": "**",
"function": "<functionName>"
}
]
},
"functions": {
"source": "<anotherDir>"
}
}
multiple Hosting site with Cloud Function rewrite
{
"hosting": [
{
"site": "blog",
"public": "<someDir>",
"rewrites": [
{
"source": "**",
"function": "<functionName>"
}
]
},
{
// another site config
}
],
"functions": {
"source": "<anotherDir>"
}
}
To correctly lookup the blog
site, hostingSite
will need to be set in svelte.config.js
:
import firebase from "svelte-adapter-firebase";
/** @type {import('@sveltejs/kit').Config} */
export default {
kit: {
adapter: firebase({ hostingSite: "blog" }),
target: "#svelte",
},
};
single Hosting site with Cloud Run rewrite
{
"hosting": {
"public": "<someDir>",
"rewrites": [
{
"source": "**",
"run": {
"serviceId": "<cloudRunServiceId>"
}
}
]
}
}
multiple Hosting site with Cloud Run rewrite
{
"hosting": [
{
"site": "blog",
"public": "<someDir>",
"rewrites": [
{
"source": "**",
"run": {
"serviceId": "<cloudRunServiceId>"
}
}
]
},
{
// another site config
}
]
}
To correctly lookup the blog
site, hostingSite
will need to be set in svelte.config.js
:
import firebase from "svelte-adapter-firebase";
/** @type {import('@sveltejs/kit').Config} */
export default {
kit: {
adapter: firebase({ hostingSite: "blog" }),
target: "#svelte",
},
};
Detailed examples of the adapter configuration options.
All options:
import firebase from "svelte-adapter-firebase";
/** @type {import('@sveltejs/kit').Config} */
export default {
kit: {
adapter: firebase({
hostingSite: "",
sourceRewriteMatch: "",
firebaseJson: "",
cloudRunBuildDir: "",
}),
target: "#svelte",
},
};
hostingSite
If firebase.json:hosting
is an array of sites, then you must provide a site
with hostingSite
to correctly match against. For example:
// firebase.json
{
"hosting": [
{
"site": "blog",
"public": "<someDir>",
"rewrites": [
{
"source": "**",
"run": {
"serviceId": "<cloudRunServiceId>"
}
}
]
},
{
"site": "adminPanel",
"public": "<anotherDir>"
}
]
}
import firebase from "svelte-adapter-firebase";
/** @type {import('@sveltejs/kit').Config} */
export default {
kit: {
adapter: firebase({ hostingSite: "blog" }),
target: "#svelte",
},
};
sourceRewriteMatch
If the rewrite source
pattern is not **
, then svelte.config.js
sourceRewriteMatch
will need to be set to match your desired rewrite rule. For example:
// firebase.json
{
"hosting": {
"public": "<someDir>",
"rewrites": [
{
"source": "/blog/**",
"run": {
"serviceId": "<cloudRunServiceId>"
}
}
]
}
}
import firebase from "svelte-adapter-firebase";
/** @type {import('@sveltejs/kit').Config} */
export default {
kit: {
adapter: firebase({ sourceRewriteMatch: "/blog/**" }),
target: "#svelte",
},
};
firebaseJson
If the firebase.json
file is not in the directory you run svelte build
, then you can set a relative path in svelte.config.js
:
.gitignore
firebase.json
app/ <-- svelte build run in this dir
package.json
svelte.config.js
src/
anotherApp/
index.html
index.css
functions/
package.json
index.js
import firebase from "svelte-adapter-firebase";
/** @type {import('@sveltejs/kit').Config} */
export default {
kit: {
adapter: firebase({ firebaseJson: "../firebase.json" }),
target: "#svelte",
},
};
cloudRunBuildDir
By default, a Node.js Cloud Run service is output to the directory named after the run.serviceId
prefixed with a .
relative to the dir in which svelte build
was executed. IE: ./.${run.serviceId}
. So with this config:
// firebase.json
{
"hosting": {
"public": "public",
"rewrites": [
{
"source": "**",
"run": {
"serviceId": "mySiteSSR"
}
}
]
}
}
will result in this output:
.mySiteSSR/ <--- Cloud Run service code
public/ <--- Hosting static assets
firebase.json
package.json
svelte.config.js
src/
app.html
routes/
index.svelte
functions/
package.json
index.js
If you wish to customise this output dir, then you can specify it in the adapter config:
import firebase from "svelte-adapter-firebase";
/** @type {import('@sveltejs/kit').Config} */
export default {
kit: {
adapter: firebase({ cloudRunBuildDir: ".special/ssr/output/dir" }),
target: "#svelte",
},
};
cloudRunBuildDir
is relative from the firebase.json
file loaded by firebaseJson
option (which has default ./firebase.json
).
With this firebase.json
and functions/
dir in a standard SvelteKit app structure and default svelte-adapter-firebase
config:
// firebase.json
{
"hosting": {
"public": "myApp",
"rewrites": [
{
"source": "**",
"function": "ssrServer"
}
],
"predeploy": ["npm run build"]
},
"functions": {
"source": "functions"
}
}
firebase.json ("public": "myApp")
package.json
svelte.config.js
src/
app.html
routes/
index.svelte
functions/
package.json ("main": "index.js")
index.js
sveltekit/ <-- Server assets
myApp/ <-- Static assets to go to Firebase Hosting CDN
The firebase.json functions.source
dir is used to find functions/package.json
whose main
field is used to find the Cloud Function build dir. This is used as the server asset output dir.
TypeScript Cloud Functions
Because we use the above method to determine the output dir the server assets are output to the correct place when using TypeScript.
firebase.json ("public": "myApp")
package.json
svelte.config.js
src/
app.html
routes/
index.svelte
functions/
package.json ("main": "lib/index.js")
index.ts
lib/
index.js
sveltekit/ <-- Server assets output to functions/lib
myApp/ <-- Static assets to go to Firebase Hosting CDN
Output with Multiple Sites
In a multi-site setup, the site
name from hosting config in firebase.json
is used as the server output dir:
firebase.json ("site": "myCoolSite","public": "myApp")
package.json
svelte.config.js
src/
app.html
routes/
index.svelte
functions/
package.json
index.js
myCoolSite/ <-- Server assets
myApp/ <-- Static assets to go to Firebase Hosting CDN
The final piece is to write the actual Cloud Function source code to reference the output server assets. The code is printed during svelte build
and should be placed in your index.js
or index.ts
manually.
This is a flexible solution that allows integrating with other Cloud Functions in your project. You can edit the provided code as you see fit. The import/require of the generated code will not change unless you change the firebase.json:hosting.site
or package.json:main
fields, so you shouldn't need to update this code after adding it.
Test your production build locally before pushing to git or deploying!
- build your app:
svelte-kit build
- install Function dependencies:
pnpm install --prefix functions
- start the emulator:
firebase emulators:start
firebase deploy
🎉
With this firebase.json
a standard SvelteKit app structure and default svelte-adapter-firebase
config:
// firebase.json
{
"hosting": {
"public": "<someDir>",
"rewrites": [
{
"source": "**",
"run": {
"serviceId": "mySiteSSR"
}
}
]
}
}
will result in this output:
.mySiteSSR/ <--- This contains the Cloud Run service code
firebase.json
package.json
svelte.config.js
src/
app.html
routes/
index.svelte
functions/
package.json
index.js
See the official Hosting/Cloud Run docs here for more setup information (enabling required APIs etc).
For those interested, we support Cloud Run with the same JS code as Cloud Functions, via the NodeJS Functions Framework and reliance on the Node.js 14 Buildpacks, which is what essentially powers Cloud Functions.
Cloud Run cannot be tested locally with the Firebase Emulator. However, you can still build and run it locally with gcloud
cli:
gcloud beta code dev --builder
This will build the container using the Google Node 14 buildpack image, run the image locally, and rebuild the image on code changes. For more details, see - https://cloud.google.com/run/docs/testing/local#cloud-sdk_
When you route to the hosted image you should be able to navigate your Cloud Run app but your CDN hosted resources (css, images, etc) will not load properly. This can be used as a sanity check
gcloud
CLI is required to build & deploy Cloud Run services. The recommended build & deploy command for Cloud Run will be output when the adapter is run.
gcloud beta run deploy ${serviceId} --platform managed --region ${region} --source ${serverOutputDir} --allow-unauthenticated
Notably, this command builds and deploys your container, which is traditionally a two step process for container runtimes. You can orchestrate your deployment however you wish. Feel free to modify the command with any other Cloud Run features you may want to use, like increasing the concurrency
or setting min_instances
This deploy command uses Cloud Build and the aforementioned Buildpacks and Functions Framework.
firebase deploy --only hosting
alongside your Cloud Run deployment as your CDN content will need to be updated as the filename hashes of static resources is rewritten on each svelte-kit build
- testing of a Cloud Run service with Firebase Hosting CDN and other backend features (PubSub/Cloud Functions) will require a full deployment to a Firebase project. The suggestion is a
dev
project per engineer and manual deployments to each env to test. No environment per PR here.
Choice is a good thing, hopefully this comparison table helps you decide which compute environment is best for your application:
Feature | Functions | Run |
---|---|---|
Firebase Emulator Integration | ✔️ | ❌ |
Unified deployment - Firebase Hosting & Compute deployed together | ✔️ | ❌ |
Cold start mitigations | ❌ (stay tuned) | ✔️ (min_instances ) |
Regions | ❌ only us-central1 shown in docs (stay tuned) |
✔️ full list in docs |
Cloud Functions seems do be a better default, with some improvements coming in the future.
Write Cloud Function code directly into
.js
file instead of printing in console.
Firebase Cloud Functions have a long history of people configuring their index files completely differently, some even generating them from directories. Accommodating these would be a headache. Instead, all we look for is a match against this string, ${name} =
, where name
is your Cloud Functions name. We may make this configurable to a specific file in future.
Additionally, this allows for users to customise their Firebase Cloud Function API like runWith()
options for memory/CPU and VPC/Ingress/Egress configuration settings, without complex support for options in the adapter. This keeps the Function config where it should, close to the executing code.
Handle the deployment of the app to Firebase.
Firebase apps consist of many different services with the CLI providing optional deployments. We do not want to dictate full deployments with your frontend nor perform partial deployments if it does not fit your app. The only option then is to leave it to you 🎉
Custom Docker images
We support Cloud Run with the same JS code as Cloud Functions, via the NodeJS Functions Framework and reliance on the Node14 Buildpacks, which is what essentially powers Cloud Functions. Diverging from this would make supporting Cloud Run more difficult. The idea behind the support is to allow usage of Cloud Run features, for example, min_instances
and concurrent requests which both reduce cold starts (if that matters to you, use the CDN!)
We are open to discussion on this topic, but it was not a goal when setting out to build this adapter (consider the cost/benefit to implement this feature well).
Why is the Cloud Function code output to the terminal for me to add manually instead of being written to
functions/index.js
?
See non-goals Write Cloud Function code directly into .js
file instead of printing in console.
Firebase libs in SvelteKit
As recommended in the SvelteKit FAQ, please use Firebase JS SDK v9 as the older version of the SDK has issues and a larger bundle size.
Cold Starts
Depends on your application load. Typically, Cloud Functions will require more instances to handle the same number of requests as each Cloud Run as each CR instance can handle up to 250 (default maximum at the time of writing). Though in my experience, bumping the memory/cpu configuration dramatically reduces the response times.
Since the purpose of using this adapter is to leverage the Firebase Hosting CDN, you should consider improving the user experience with targetted caching/TTLs.
If cold starts are still an issue for your application, Cloud Run has support for min_instances
which will keep x
number of instances warm. This incurs additional costs, though as discussed by Ahmet Alp Balkan here can be cheaper than an equivalent Compute Engine instance. See the official Cloud Run pricing documentation for more.
- Firebase Hosting Preview Channels currently lacks first-party support for SSR applications. This adapter doesn't attempt to remedy this issue and doesn't produce a different SSR Function/Run for preview channel deployments.
⚠️ Cloud Function rewrites only support us-central1, other regions will error. The official warning about this can be found in these docs.
1.0.0
will not be published until the SvelteKit Adapter API is declared stable and SvelteKit is released for general use.
The adapter is intended to guide you through the configuration of your firebase.json
and svelte.config.js
file. As such, when you have a misconfiguration the adapter will log a hint. Codes for these hints are listed here:
firebase.json file
- SAF1000: provided
firebase.json
file does not exist. It is computed from adapter configfirebaseJson
. If the default adapter config is not working, consider updating it insvelte.config.js
- SAF1001:
JSON.parse
error offirebase.json
firebase.json:hosting[]
- SAF1010:
hosting
field required infirebase.json
- SAF1011: Multiple hosting configurations found, which requires each to have a
site
field, one does not. - SAF1012: Multiple
hosting
configurations found infirebase.json
but nohostingSite
specified insvelte.config.js
adapter config. - SAF1013: Multiple
hosting
configurations found infirebase.json
but no match found forhostingSite
specified insvelte.config.js
adapter config.
firebase.json:hosting[].rewrites
- SAF1020: Required
hosting[].rewrites
field not found for matched hosting config. - SAF1021: Required
hosting[].rewrites[]
does not contain a config withsource
matching adapter configsourceRewriteMatch
and eitherfunction
orrun
entries.
firebase.json:hosting[].rewrites Cloud Run config
- SAF1030: Required
serviceId
field not found for Cloud Run rewrite rule infirebase.json
. - SAF1031: Cloud Run
serviceId
is invalid. Cloud RunserviceId
must use only lowercase alphanumeric characters and dashes, cannot begin or end with a dash, and cannot be longer than 63 characters. Updatefirebase.json
accordingly. - SAF1032: Cloud Run
region
is invalid. Firebase Hosting rewrites only support"regions":"us-central1"
. Updatefirebase.json
accordingly.
firebase.json:hosting[].rewrites Cloud Function config
- SAF1040: Function name for rewrite rule is invalid. Function name must use only alphanumeric characters and underscores and cannot be longer than 62 characters. Update
firebase.json
accordingly.
firebase.json:hosting[].public
- SAF1050: Required
hosting.public
field not found for hosting configuration. Add apublic
field to the matched hosting config infirebase.json
. - SAF1051:
firebase.json:hosting.public
must be a different directory tosvelte.config.js:kit.files.assets
. - SAF1052: Required "hosting.public" field is an empty string. "public" should be a directory name.
firebase.json:functions
- SAF1060: Required
functions.source
field missing fromfirebase.json
file. - SAF1061: Node.js runtime not supported. SvelteKit on Cloud Functions requires Node.js 14 or newer runtime. Set the version in either:
<cloud function dir>/package.json:engines.node
orfirebase.json:functions.runtime
.
Contributions of any kind welcome, just follow the guidelines!
Short version:
git clone https://github.com/jthegedus/svelte-adapter-firebase.git
asdf install
pnpm i
See asdf to install set it up.
While building this adapter some issues were found with upstream components, these are captured here should someone wish to contribute them:
- Cloud Function validation code linked in
utils.js
is from two different sources which indicates that it is being validated byfirebase-tools
in two separate places. PR a fix there.