If you have a Vite/React/TypeScript project you can integrate Keycloakify directly inside it.
In this guide we're going to work with a vanilla Vite project.
Creating a new vite project with yarn create vite. You don't need to create a new project. Just use your existing codebase.
Our codebase before installing Keycloakify
{% hint style="info" %} Before anything make sure to commit all your pending changes so you can easily revert changes if need be. {% endhint %}
Let's start by installing Keycloakify (and optionally Storybook) to our project:
{% tabs %} {% tab title="yarn" %}
yarn add keycloakify
yarn add --dev storybook @storybook/react @storybook/react-vite
{% endtab %}
{% tab title="pnpm" %}
pnpm add keycloakify
pnpm add --dev storybook @storybook/react @storybook/react-vite
{% endtab %}
{% tab title="bun" %}
bun add keycloakify
bun add --dev storybook @storybook/react @storybook/react-vite
{% endtab %}
{% tab title="npm" %}
npm install --save keycloakify
npm install --save-dev storybook @storybook/react @storybook/react-vite
{% endtab %} {% endtabs %}
Next we want to repatriate the relevant files from the starter template into our project:
cd my-react-app
git clone https://github.com/keycloakify/keycloakify-starter tmp
mv tmp/src src/keycloak-theme
mv tmp/.storybook .
rm -rf tmp
rm src/keycloak-theme/vite-env.d.ts
mv src/keycloak-theme/main.tsx src/main.tsx
State of your codebase after bringin in the Keycloakify boilerplate code.
Note thate the keycloak-theme (or keycloak_theme) directory can be located anywhere under your src directory.
Now you want to modify your entry point so that:
- If the kcContext global is defined, render your Keycloakify theme
- Else, reder your App as usual.
Let's say, for example, your src/main.tsx file currently looks like this:
{% code title="src/main.tsx" %}
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
import "./index.css";
import { MyProvider } from "./MyProvider";
createRoot(document.getElementById('root')!).render(
<StrictMode>
<MyProvider>
<App />
</MyProvider>
</StrictMode>,
);
{% endcode %}
You want to rename this file to src/main.app.tsx (for example) and modify it as follow:
{% code title="src/main.app.tsx" %}
import App from "./App.tsx";
import "./index.css";
import { MyProvider } from "./MyProvider.tsx";
export default function AppEntrypoint() {
return (
<MyProvider>
<App />
</MyProvider>
)
}
{% endcode %}
{% hint style="info" %}
If you have some top level await
and you don't know how to deal with thoses, join the discord server, I can help you out.
{% endhint %}
Then you want to create the following src/main.tsx file, you can copy paste the followint code, it does not need to be adapted:
{% code title="src/main.tsx" %}
import { createRoot } from "react-dom/client";
import { StrictMode, lazy, Suspense } from "react";
import { KcPage, type KcContext } from "./keycloak-theme/kc.gen";
const AppEntrypoint = lazy(() => import("./main.app"));
// The following block can be uncommented to test a specific page with `yarn dev`
// Don't forget to comment back or your bundle size will increase
/*
import { getKcContextMock } from "./keycloak-theme/login/KcPageStory";
if (import.meta.env.DEV) {
window.kcContext = getKcContextMock({
pageId: "register.ftl",
overrides: {}
});
}
*/
createRoot(document.getElementById("root")!).render(
<StrictMode>
{window.kcContext ? (
<KcPage kcContext={window.kcContext} />
) : (
<Suspense>
<AppEntrypoint />
</Suspense>
)}
</StrictMode>
);
declare global {
interface Window {
kcContext?: KcContext;
}
}
{% endcode %}
{% hint style="info" %} Question:
Why do my main application and Keycloak theme share the same entry point?
Answer:
To simplify the build process. If you don't want it to negatively impact the performance of your application, it's essential to understand the following points:
- Different Contexts: The application (
App
) and Keycloak page (KcPage
) are mounted in very different contexts. Avoid sharing providers between the two at themain.tsx
file level. The true entry point of your application is theAppEntrypoint
component defined inmain.app.tsx
, while the entry point for your Keycloak theme is theKcPage
component. Be careful about what code is shared between them. - Responsibility of main.tsx: The
main.tsx
file should only determine the context (either the application or Keycloak) and mount the appropriate component (App
orKcPage
). It should not contain any substantial logic or dependencies. - Performance Considerations: Keep
main.tsx
as lightweight as possible to avoid increasing the initial load time of both your main application and login pages. For example, do not load any state management libraries likeredux-toolkit
at this level. {% endhint %}
You also need to use Keycloakify's Vite plugin. Here we don't provide any build options but you probably at least want to define keycloakVersionTargets.
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { keycloakify } from "keycloakify/vite-plugin";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
react(),
keycloakify({
accountThemeImplementation: "none"
})
],
})
{% hint style="info" %}
Leave accountThemeImplementation set to "none" for now.
To initialize the account theme refer to this guide.
{% endhint %}
Finally you want to add to your package.json a script for building the theme and another one to start storybook.
{
"name": "my-react-app",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview",
"build-keycloak-theme": "npm run build && keycloakify build",
"storybook": "storybook dev -p 6006"
},
// ...
Last setp is to exclude from your html <head />
things that aren't relevent in the context of Keycloak pages.
{% hint style="danger" %} Do not blindely copy paste, this is just an example!
You have to figure out what does and does not make sense to be in the <head/> of your Keycloak UI pages. {% endhint %}
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="keycloakify-ignore-start">
<title>ACME Dashboard</title>
<script>
window.ENV = {
API_ADDRESS: '${API_ADDRESS}',
SENTRY_DSN: '${SENTRY_DSN}'
};
</script>
<meta name="keycloakify-ignore-end">
<!-- ... -->
</head>
<!-- ... -->
In the above example we tell keycloakify not to include the <title>
because keycloakify will set it dynamically to something like "ACME- Login" or "ACME - Register".
We also exclude a placeholder script for injecting environnement variables at container startup.
That's it, your project is ready to go! 🎉
You can run npm run build-keycloak-theme, the JAR distribution of your Keycloak theme will be generated in dist_keycloak.
You're now able to use all the Keycloakify commands (npx keycloakify --help
) from the root of your project.
{% hint style="success" %} If you're currently using keycloak-js or react-oidc-context to manage user authentication in your app you might want to checkout oidc-spa, the alternative from the Keycloakify team.
If you have any issues reach out on Discord! We're here to help! {% endhint %}
{% content-ref url="../../testing-your-theme/" %} testing-your-theme {% endcontent-ref %}
{% content-ref url="../../customization-strategies/" %} customization-strategies {% endcontent-ref %}