Run
bun i && cd bun && bun i && cd ../astro_embed && bun i && cd ..
TODO: install as monorepo
bun run dev
// import theme context
const layout = useContext(themeContext);
layout.value = 'Theme1';
// import layout context
const layout = useContext(layoutContext);
const loc = ueLocation();
const nav = useNavigate();
layout.value = 'Layout1';
// location.reload can also be used but all states will be lost
nav(loc.url.href, {
forceReload: true,
});
Now for each page under Root (excluding Root), it will try to look for the export with the layout name and render it. If the export is not found, it fallbacks to rendering default export.
-
We are currently not supporting the switching of locale because we don't have enough content that is written in languages other than English. We plan to support it in the future.
-
TODO
-
Proposal
- Priority: url [.lang] params > cookies
To add a page that supports internationalization:
Add a layout file that includes the useSpeak({assets: ["pageName"]})
. Key in the route file should be pageName.A.B...
and the keys in the document head should be app.pageName.head.title
or app.pageName.head.description
, etc.
To add a locale:
Edit the lang.json
and also qwik-speak-extract
script in package.json.
Go to package.json
and chnage the build.server
command, and also index.mjs
in api
folder.
Current: Qwik 1.3.1 (Qwik and Qwik-city should have the same version)
-
Feat: optional param routing and allowedParams
In packages\qwik-city\buildtime\types.ts, in
PluginOptions
, add:allowedParams?: Record<string, string[]>;
In packages\qwik-city\runtime\src\types.ts, in
QwikCityPlan
, add:readonly allowedParams?: Record<string, string[]>;
In packages\qwik-city\runtime\src\qwik-city-plan.ts, add:
export const allowedParams = {};
and ensure
allowedParams
is exported like this:export default { routes, menus, trailingSlash, basePathname, cacheModules, allowedParams, };
Replace packages\qwik-city\runtime\src\route-matcher with doc\qwik-city\route-matcher
Replace packages\qwik-city\runtime\src\routing with doc\qwik-city\routing
Replace packages\qwik-city\middleware\request-handler\request-handler with doc\qwik-city\request-handler
In packages\qwik-city\buildtime\vite\dev-server.ts, replace
matchRouteRequest
function with the following:const matchRouteRequest = (pathname: string) => { for (const route of ctx.routes) { let params = matchRoute(route.pathname, pathname, ctx.opts.allowedParams); if (params) { return { route, params }; } if (ctx.opts.trailingSlash && !pathname.endsWith('/')) { params = matchRoute(route.pathname, pathname + '/', ctx.opts.allowedParams); if (params) { return { route, params }; } } } return null; };
In scripts\api.ts, replace
referenceDts
with the following:const referenceDts = ` declare module '@qwik-city-plan' { export const routes: any[]; export const menus: any[]; export const trailingSlash: boolean; export const basePathname: string; export const cacheModules: boolean; export const allowedParams = {}; const defaultExport: { routes: any[]; menus: any[]; trailingSlash: boolean; basePathname: string; cacheModules: boolean; allowedParams: Record<string, string[]> }; export default defaultExport; } `;
In packages\qwik-city\buildtime\runtime-generation\generate-qwik-city-plan.ts, add the following line:
c.push(`export const allowedParams = ${JSON.stringify(ctx.opts.allowedParams)};`);
and make sure
allowedParams
is exported like this:c.push(`export default { routes, serverPlugins, menus, trailingSlash, allowedParams, basePathname, cacheModules };`);
In packages\qwik-city\runtime\src\qwik-city-component.tsx, for every loadRoute called (should be 3 in total), add
qwikCity.allowedParams
as the last argument.In packages\qwik-city\utils\fs.unit.ts,
packages\qwik-city\buildtime\markdown\markdown-url.unit.ts,
packages\qwik-city\buildtime\routing\resolve-source-file.unit.ts,
whenever there isconst opts: NormalizedPluginOptions
, add the following to the object: (should have 4)allowedParams: { }
-
Exposing loadClientData for manual prefetching and adding removeClientDataCache
In packages\qwik-city\runtime\src\use-endpoint.ts, add the @public flag before the loadClientData function declaration:
/** @public */ export const loadClientData = async (...
then add the function removeClientDataCache:
/** @public */ export const removeClientDataCache = () => { const keys = CLIENT_DATA_CACHE.keys(); for (const key of keys) { CLIENT_DATA_CACHE.delete(key); } };
And then in packages\qwik-city\runtime\src\index.ts, add the following to export the function:
export { loadClientData, removeClientDataCache } from './use-endpoint';
-
Prefetch links on visible instead of hover
Prefetching links on hover is slow and have a noticeable delay. Besides, prefetching links when it enter the viewport is the default prefetching strategy for many other frameworks.
In packages\qwik-city\runtime\src\link-component.tsx, change the if condition of
prefetchLinkResources
to:if (isOnVisible) { // either this is a mouseover event, probably on desktop // or the link is visible, and the viewport width is less than X loadClientData(new URL(elm.href), elm); }
-
Feat: Adding in layout switching
We will add in a feature in router outlet that reads the layout attribute and render the page accordingly, if the child with the name identical as the layout is not exported from the route, the default child will be rendered.
Replace packages\qwik-city\runtime\src\router-outlet-component with doc/qwik-city/router-outlet-component
Check if we need to integrate any new features implemented in router outlet from qwik!!!
Then in packages\qwik-city\runtime\src\types.ts, in PageModule and LayoutModule, add the following key value pair:
readonly [x: string]: any;
-
Building and Pushing to Github
pnpm install
(delete all pnpm folders in user/AppData/local, remove node_modules folder and reinstall pnpm if operation not permitted error shows)pnpm api.update
bun build.full
Go to qwik-city/bin and do git init and set remote main to
https://github.com/samyung0/qwikcity-dist
, then commit all files and push:cd packages/qwik-city/lib git init git remote add origin https://github.com/samyung0/qwikcity-dist git add . git commit -m "init" git push origin main -f
cd packages/qwik-city/lib && git init && git remote add origin https://github.com/samyung0/qwikcity-dist && git add . && git commit -m "init" && git push origin main -f
-
QWIK Bugs
- DO NOT use
await server$()()
pattern. It only works in development mode but givesdynamic require is not supported
error in prod.
- DO NOT use
-
Installing ElysiaJS dependencies twice
Since we are using typesafe api calls via edenTreaty in Bun, we need to install the dependencies used by ElysiaJS in our root folder for the typescript to pickup. However, when we are deploying the bun app, only the files inside of
bun
will be deployed (to save space since we don't need to install so many dependencies). This creates a problem where we need to install any dependency used by ElysiaJS twice: once in the root folder, and once inside ofbun
folder. -
Cloudinary uploads and assets are all public
The app id of cloudinary is visible publicly but the upload present is not. Technically even unsigned requests to the api endpoints require an upload present id, it is still better to assume that anyone can upload to cloudinary at the moment. And all images that are uploaded this way can also be fetched anywhere. Signed uploads require backend Sdk which mean we need to configure both Lucia auth and Cloudinary Sdk on Bun api server. (https://support.cloudinary.com/hc/en-us/articles/202519742-Can-I-allow-access-to-uploaded-images-only-to-authenticated-users-[https://support.cloudinary.com/hc/en-us/articles/202519742-Can-I-allow-access-to-uploaded-images-only-to-authenticated-users-])
-
Good Practice: Do not prefetch routes that require auth
An issue is that if the user prefetches a route while authed, and then logouts while staying on the same page and clicks into the prefetched page, the user can visit the site as authed since Qwik already stores a version of the authed site in serialized data and it will not fetch the site data again since it is cached already.
One of the solution is to clear the client data cache that persists route info by creating a function inside of qwik-city, and calling the function everytime there is an auth change.
-
Routing and Embedding
Webcontainer from stackblitz requires cross origin opener policy (COOP) header set to same-origin and cross origin embedder policy (COEP) header set to require-corp in order to establish cross origin isolation. You can go into developer tab and type in
crossOriginIsolated
. Check the effects of the headers here.TL;DR
- new windows opened in the crossOriginIsolated route must have the same origin (domains) in order to interact with the parent window
- whenever a video/audio/image/iframe is embedded, certain procedures needed to be done to ensure embedding is allowed
- for iframe: either it responses with COEP require-corp, or use credentialless in the iframe tag (check support)
- for image: use crossorigin for the img tag, or responses with COEP require-corp
Implications:
- Youtube embeds do not work on crossOriginIsolated routes (See issue tracked)
Setting a route to be crossOriginIsolated:
- In development environment with vite, use middleware and test for the req.url
- In production with vercel, add the headers for the relevant paths (make sure it ends with a slash, it's a qwik thing to redirect every route except root to one ending with slash) in vercel.json
-
Finish up Stripe payment page
- Put subscription in another page other than profile
- Show next billing date
- Show pending cancel plan (cancelled but still active until next billing date)
- Make a billing page
- Show billing history
-
Configure cloudinary to store private images
-
Responsive Design
-
Add search to dashboard 'my courses'
-
Use onchange from webcontainer instead of chokidar (need node v20 !!)
-
Add browser version check
- web workers
- @webcontainer/api
- monaco
We can then set vite compile target accordingly
-
Installing github app and fetching repo
-
Consider switching from webcontainer to sandpack by codesandbox
There are currently too many problems with webcontainer, like the crossOriginIsolation, some node functionalities (async storage) not getting implemented, etc.
-
Enable dynamic import of themes
- and possible use a theme switcher library
-
Configure MicroVM
- amazon ec2?
- we need authentication, resource mangement, microvm & docker management, socket connection (or RTC idk), theres a lot
This starter site is configured to deploy to Netlify Edge Functions, which means it will be rendered at an edge location near to your users.
The Netlify CLI can be used to preview a production build locally. To do so: First build your site, then to start a local server, run:
- Install Netlify CLI globally
npm i -g netlify-cli
. - Build your site with both ssr and static
bun build
. - Start a local server with
bun serve
. In this project,bun serve
uses thenetlify dev
command to spin up a server that can handle Netlify's Edge Functions locally. - Visit http://localhost:8888/ to check out your site.
Netlify Edge Functions declarations can be configured to run on specific URL patterns. Each edge function declaration associates one site path pattern with one function to execute on requests that match the path. A single request can execute a chain of edge functions from a series of declarations. A single edge function can be associated with multiple paths across various declarations.
This is useful to determine if a page response should be Server-Side Rendered (SSR) or
if the response should use a static-site generated (SSG) index.html
file instead.
By default, the Netlify Edge adaptor will generate a .netlify/edge-middleware/manifest.json
file, which is used by the Netlify deployment to determine which paths should, and should not, use edge functions.
To override the generated manifest, you can add a declaration to the netlify.toml
using the [[edge_functions]]
config. For example:
[[edge_functions]]
path = "/admin"
function = "auth"
Netlify-specific option fields that can be passed to the adapter options:
excludedPath
this option accepts astring
glob pattern that represents which path pattern should not go through the generated Edge Functions.
You can deploy your site to Netlify either via a Git provider integration or through the Netlify CLI. This starter site includes a netlify.toml
file to configure your build for deployment.
Once your site has been pushed to your Git provider, you can either link it in the Netlify UI or use the CLI. To link your site to a Git provider from the Netlify CLI, run the command:
netlify link
This sets up continuous deployment for your site's repo. Whenever you push new commits to your repo, Netlify starts the build process..
If you wish to deploy from the CLI rather than using Git, you can use the command:
netlify deploy --build
You must use the --build
flag whenever you deploy. This ensures that the Edge Functions that this starter site relies on are generated and available when you deploy your site.
Add --prod
flag to deploy to production.
Cloudflare's wrangler CLI can be used to preview a production build locally. To start a local server, run:
npm run serve
Then visit http://localhost:8787/
Cloudflare Pages are deployable through their Git provider integrations.
If you don't already have an account, then create a Cloudflare account here. Next go to your dashboard and follow the Cloudflare Pages deployment guide.
Within the projects "Settings" for "Build and deployments", the "Build command" should be npm run build
, and the "Build output directory" should be set to dist
.
Cloudflare Page's function-invocation-routes config can be used to include, or exclude, certain paths to be used by the worker functions. Having a _routes.json
file gives developers more granular control over when your Function is invoked.
This is useful to determine if a page response should be Server-Side Rendered (SSR) or if the response should use a static-site generated (SSG) index.html
file.
By default, the Cloudflare pages adaptor does not include a public/_routes.json
config, but rather it is auto-generated from the build by the Cloudflare adaptor. An example of an auto-generate dist/_routes.json
would be:
{
"include": [
"/*"
],
"exclude": [
"/_headers",
"/_redirects",
"/build/*",
"/favicon.ico",
"/manifest.json",
"/service-worker.js",
"/about"
],
"version": 1
}
In the above example, it's saying all pages should be SSR'd. However, the root static files such as /favicon.ico
and any static assets in /build/*
should be excluded from the Functions, and instead treated as a static file.
In most cases the generated dist/_routes.json
file is ideal. However, if you need more granular control over each path, you can instead provide you're own public/_routes.json
file. When the project provides its own public/_routes.json
file, then the Cloudflare adaptor will not auto-generate the routes config and instead use the committed one within the public
directory.
This app has a minimal Cloud Run server implementation. After running a full build, deploy your app with:
npm run deploy
This app has a minimal Cloud Run server implementation. After running a full build, deploy your app with:
npm run deploy