Angular + Vercel
Jeżeli dopiero zaczynasz przygodę z Angularem, z pewnością chciałbyś mieć możliwość udostępnienia swoich projektów na zewnątrz. W oficjalnej dokumentacji, możemy znaleźć kilka receptur na deployment aplikacji za pomocą serwisów typu Firebase, Github Pages lub Netlify. Każdy z wymienionych serwisów ma swój własny proces wdrażania aplikacji. W nadchodzącej serii artykułów przeanalizujemy niektóre z nich. Dzisiaj pod lupę weźmiemy jedną z opcji jaką jest Vercel.
Vercel
Vercel to potężna platforma do wdrażania aplikacji webowych. Oferuje nam szereg ciekawych funkcjonalności, które przeanalizuję w dalszej części. Vercel słusznie kojarzy się głównie ze swoim meta-frameworkiem Next.js, bazującym na bibliotece React. Nie oznacza to jednak, że nie możemy wdrożyć aplikacji napisanej w innych technologiach, np. w Angularze. Vercel w swojej ofercie ma darmowy plan hobby, który świetnie sprawdzi się do naszych side projectów.
Tworzenie aplikacji
Na samym początku wygenerujmy nową aplikację za pomocą Angular CLI. Zamiast standardowego npm’a użyję pnpm jako package managera. Pnpm jest alternatywą dla npm’a, która oferuje nam szybszą instalacje paczek oraz lepszą wydajność. W przypadku późniejszego deployowania aplikacji, będzie zależało nam na czasie budowania, dlatego jest to niewątpliwie zaleta. Możemy to zrobić dodając flagę --package-manager={npm|yarn|pnpm|cnpm}
do naszej komendy.
1 |
ng new ng-vercel-app --package-manager=pnpm |
Deploy z Vercel CLI
Projekt stworzony. Aby wdrożyć nasz projekt na Vercel możemy połączyć naszą aplikację z repozytorium na popularnych platformach, jak np. Github lub za pomocą Vercel CLI. Do podstawowego korzystania z platformy, połączenie zdalnego repozytorium w pełni wystarczy. Instalujemy Vercel CLI jako globalną paczkę w pnpm.
1 |
pnpm i -g vercel |
Po instalacji możemy zweryfikować czy paczka została poprawnie zainstalowana.
1 2 |
$ vercel --version > Vercel CLI 28.15.6 |
Vercel CLI zainstalowane, możemy teraz przejść do wdrożenia naszej aplikacji. Z poziomu głównego katalogu uruchamiamy komendę vercel
, która uruchomi konfigurator naszego projektu na platformie. Jeżeli nigdy wcześniej nie używaliśmy Vercel CLI, zostaniemy poproszeni o zalogowanie się do naszego konta.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
$ vercel > Vercel CLI 28.15.6 > ? Set up and deploy “~/projects/ng-vercel-app”? [Y/n] y > ? Which scope do you want to deploy to? dyqmin > ? Link to existing project? [y/N] n > ? What’s your project’s name? ng-vercel-app > ? In which directory is your code located? ./ > Local settings detected in vercel.json: > Auto-detected Project Settings (Angular): > - Build Command: ng build > - Development Command: ng serve --port $PORT > - Install Command: `yarn install`, `pnpm install`, or `npm install` > - Output Directory: dist > ? Want to modify these settings? [y/N] n > 🔗 Linked to dyqmin/ng-vercel-app (created .vercel and added it to .gitignore) > 🔍 Inspect: https://vercel.com/dyqmin/ng-vercel-app/HJMawZjUPo5NUD4PjVeHLYTttZgY [1s] > ✅ Production: https://ng-vercel-app.vercel.app [49s] > 📝 Deployed to production. Run `vercel --prod` to overwrite later (https://vercel.link/2F). > 💡 To change the domain or build command, go to https://vercel.com/dyqmin/ng-vercel-app/settings |
Jak widać, Vercel automatycznie wykrył naszą aplikację jako Angularową i zainicjował konfigurację z gotowego presetu. W outpucie dostaliśmy linki do naszego środowiska produkcyjnego aplikacji oraz odnośnik do naszego projektu na Vercelu.
Zróbmy teraz prostą zmianę w kodzie i zobaczmy jak wygląda proces deployowania aplikacji przy użyciu komendy vercel deploy
1 2 3 4 5 |
$ vercel deploy > Vercel CLI 28.15.6 > 🔍 Inspect: https://vercel.com/dyqmin/ng-vercel-app/8PkfJv7MvqKwKuDz5kYi6sCFF6BYw [4s] > ✅ Preview: https://ng-vercel-app-dyqmin.vercel.app [31s] > 📝 To deploy to production (ng-vercel-app.vercel.app), run `vercel --prod` |
Vercel dla każdego deploymentu tworzy nam środowisko preview, które możemy przetestować przed wdrożeniem na produkcję. Możemy sprawdzić zmiany przez wizualny podgląd, a następnie przerzucić je na środowisko produkcyjne, jak podpowiedziało wyżej CLI. W tym celu wystarczy wywołać komendę vercel --prod
.
Połączenie z repozytorium na GitHubie
W poprzednim kroku połączyliśmy lokalną aplikacje z Vercelem. Warto dodać, że Vercel oferuje nam możliwość deployowania aplikacji bezpośrednio z repozytorium na popularnych platformach, takich jak GitHub, GitLab, Bitbucket. Jest to zdecydowanie wygodniejsze rozwiązanie.
W panelu na stronie Vercela przechodzimy do nowo utworzonego projektu.
Na górze mamy przycisk, który przeniesie nas do ustawień projektu wraz z listą dostępnych repozytoriów konta, które powiązaliśmy z Vercelem.
Od teraz, każdy push do repozytorium uruchomi automatyczny proces deployowania aplikacji.
Comments
Jak widzimy, deployment aplikacji jest bajecznie prosty. Po wejściu na preview url, możemy dostrzec dodatkowy, statyczny panel w dolnej sekcji naszej strony.
Jest to dedykowana dla platformy nakładka, dzięki której możemy między innymi umieszczać komentarze przypinając je do danej sekcji strony. Jest to świetne narzędzie do dyskusji i dzielenia się uwagami z zespołem. Jest w pełni interaktywna – możemy tagować innych użytkowników, tworzyć wątki oraz dostawać notyfikację. Dla darmowego planu hobby może służyć jako notatki. Gdyby panel w jakikolwiek sposób nam przeszkadzał, możemy go ukryć lub w pełni wyłączyć ten feature w ustawieniach.
Vercel Integrations
Są to gotowe integracje z różnymi zewnętrznymi, a także wewnętrznymi serwisami i narzędziami. Do wyboru mamy przeróżne opcje w postaci popularnych narzędzi do monitoringu, analityki, CMS i wiele więcej. Ilość integracji stale rośnie, więc jeżeli nie znajdziesz swojego ulubionego narzędzia, być może pojawi się w niedalekiej przyszłości! https://vercel.com/integrations
Jako pierwszą integracje, do swojego projektu dodam Vercel Analytics. Na dzień pisania artykułu Analytics jest w fazie Beta, przez co jest darmowy. Może się to zmienić w przyszłości. Włącza się go nietypowo w porównaniu do reszty integracji, bo w głównym panelu naszego projektu, w zakładce “Analytics”. Poniżej możemy skonfigurować Web Vitals lub Audiences. My zajmiemy się zakładką “Audiences”, czyli ubogim Google Analyticsem do pobierania podstawowych metryk, takich jak liczba wejść na strony, ich źródła oraz liczba odwiedzających. Po wejściu w zakładkę, uruchamiamy moduł przyciskiem “Enable”.
Zliczanie statystyk musimy zacząć od instalacji oficjalnej paczki:
1 |
pnpm install @vercel/analytics |
Teraz wystarczy dodać funkcję inicjalizującą skrypt analityczny w procesie bootstrapu naszej aplikacji. Dodajemy więc odpowiedni kawałek kodu do naszej aplikacji w pliku main.ts
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import { APP_INITIALIZER, isDevMode } from "@angular/core"; import { bootstrapApplication } from "@angular/platform-browser"; import { provideRouter } from "@angular/router"; import { inject } from "@vercel/analytics"; import { AppComponent } from "./app/app.component"; import { routes } from "./app/routes"; bootstrapApplication(AppComponent, { providers: [ provideRouter(routes), { provide: APP_INITIALIZER, useFactory: () => { inject({ mode: isDevMode() ? 'development' : 'production' }) } } ] }) .catch(err => console.error(err)); |
Po odpaleniu aplikacji, zobaczymy w konsoli logi z prefixem [Vercel Analytics], służące do debugowania zdarzeń pobieranych przez skrypt. Debugowanie jest włączone automatycznie jeżeli zmienna środowiskowa NODE_ENV
ma wartość developement
lub test
, natomiast możemy je wyłączyć dodając odpowiednią flagę w propsach injecta:
1 |
inject({ debug: false }) |
Dodatkowo możemy zauważyć, że funkcja dodała do dokumentu skrypt Vercela na końcu head’a:
1 |
<script src="https://cdn.vercel-insights.com/v1/script.debug.js" defer="" data-sdkn="@vercel/analytics" data-sdkv="0.1.11"></script> |
Oznacza to, że wszystko działa jak należy! Commitujemy, pushujemy i sprawdzamy deployment! Po testowych wejściach na produkcyjny link, wracamy do panelu i możemy potwierdzić, że dane odkładają się prawidłowo.
Analytics jest jedną z podstawowych integracji, do innych jeszcze wrócimy 🙂
Serverless Functions
Vercel oferuje tworzenie serverless functions z poziomu projektu, które zostaną automatycznie zmapowane i wdrożone przy regularnym deploymencie apki. Za moment sprawdzimy jak prosty jest proces ich tworzenia.
W darmowym planie Hobby, możemy stworzyć maksymalnie 12 funkcji. Dostajemy również limit maksymalnego czasu wykonywania wynoszący 10 sekund.
Dla szybszego czasu otrzymania odpowiedzi z naszych funkcji, sprawdźmy region deploymentu w ustawieniach projektu w panelu. Miałem ustawioną domyślną lokalizację ze Stanów Zjednoczonych, dlatego zmieniam na najbliższy dla mnie Frankfurt.
Zabawę z vercelowymi funkcjami zaczynamy od instalacji paczek. Będą to definicje typów.
1 |
pnpm install -D @vercel/node @types/node |
W naszym głównym katalogu tworzymy folder api. Każdy plik *.js/*.ts utworzony w tym folderze zostanie zinterpretowany jako serverless function oraz zmapowany na adres identyczny jak nazwa pliku.
Oznacza to, że utworzenie pliku o nazwie cats.ts
zbuduje nam endpoint o ścieżce /api/cats
. Wyjątkiem jest plik o nazwie index
, który definiowany jest jako punkt dostępowy bieżącego katalogu. Umieszczając plik index.ts
w folderze dogs, ścieżka naszego endpointu będzie wyglądała tak: /api/dogs
.
Stwórzmy więc nasz pierwszy endpoint w pliku o nazwie hello.ts. Dla przypomnienia, zostanie on zmapowany do ścieżki /api/hello.
1 2 3 4 5 6 7 |
import { VercelRequest, VercelResponse } from "@vercel/node"; export default function (request: VercelRequest, response: VercelResponse) { response.status(200).send({ message: 'Hello World!', }); }; |
Do funkcji przekazywany jest request oraz response, co pozwala nam na dostęp do cookies, query parameters i innych właściwości interfejsu Request
. W podobny sposób tworzylibyśmy endpoint w bibliotece typu express.js
. Jak możemy wywnioskować z kodu, mutujemy response zwracając status 200 wraz z odpowiedzią w postaci obiektu z polem ‘message’.
Aby uniknąć błędów kompilacji plików typescript, dodajmy tsconfig.json wewnątrz folderu api.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
{ "compilerOptions": { "target": "ES2015", "module": "CommonJS", "moduleResolution": "node", "lib": [ "ES2020", ], "strict": true, "noImplicitAny": true, "strictNullChecks": true, "strictFunctionTypes": true, "strictBindCallApply": true, "strictPropertyInitialization": true, "noImplicitThis": true, "alwaysStrict": true, "esModuleInterop": true, "types": ["node"] }, } |
Standardowe serwowanie aplikacji przez komendę ng serve nie wystarczy do uruchomienia naszej funkcji, ponieważ angularowy builder nie ma pojęcia o istnieniu i mechanizmie katalogu api. Vercel CLI oferuje dedykowany serwer lokalny, który wrapuje komendy z Angularowego CLI i jednocześnie imituje działanie serverless functions uruchamiając je na dodatkowym procesie. Uruchamiamy go komendąvercel dev. Serwer domyślnie wystartuje na porcie 3000, jeżeli chcemy korzystać ze standardowego portu 4200, możemy to zmienić dodając argument
listen.
1 |
vercel dev --listen 4200 |
Po uruchomieniu wejdźmy pod adres localhost:4200/api/hello aby sprawdzić czy nasz endpoint działa.
Świetnie! Połączmy teraz naszą angularową aplikację z funkcją w nowym serwisie.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import { inject, Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { map, Observable } from 'rxjs'; @Injectable() export class ApiService { private readonly _http = inject(HttpClient); getMsg(): Observable<string> { return this._http.get<{ message: string }>('/api/hello').pipe( map((res) => res.message), ); } } |
Do przetestowania serwisu stworzyłem nowy komponent, w którym wyzwalam request async pipem.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import { Component, inject } from '@angular/core'; import { ApiService } from './api.service'; import { AsyncPipe } from '@angular/common'; @Component({ selector: 'app-home', template: `{{ msg$ | async }}`, imports: [AsyncPipe], standalone: true, }) export class HomeComponent { private readonly _apiService = inject(ApiService); readonly msg$ = this._apiService.getMsg(); } |
Możemy teraz sprawdzić działanie w przeglądarce!
Dynamiczne ścieżki
Wyżej opisałem działanie mapowania plików, ale nie wspomniałem o bardzo przydatnej funkcjonalności, jaką są dynamiczne ścieżki. Pliki oraz foldery, których nazwa znajduje się miedzy kwadratowymi nawiasami interpretowana jest jako ścieżka dynamiczna. Oznacza to, że przyjmie dowolny ciąg znaków.
Stworzę plik [name].ts, który będzie pobierał stringa z URL w miejscu do tego wyznaczonym:
1 2 3 4 5 6 7 8 9 |
import type { VercelRequest, VercelResponse } from '@vercel/node'; export default function (request: VercelRequest, response: VercelResponse) { const { name = 'World' } = request.query; response.status(200).send({ message: `Hello ${name}!`, }); }; |
W bibliotece express.js
podobny endpoint stworzylibyśmy w ten sposób:
1 2 3 |
app.get('/:name', (req , res) => { // implementation }); |
Możemy tworzyć ścieżki tworząc wiele dynamicznych fragmentów w drzewie plików. Nasza struktura katalogów może wyglądać przykładowo tak:
1 2 3 4 5 |
api/ └── users/ └── [userId]/ └── posts/ └── [postId].ts |
Podobnie jak w przykładzie wyżej, dynamiczne fragmenty zostaną przekazane jako query.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
export default function (request: VercelRequest, response: VercelResponse) { const { userId, postId } = request.query; if (!userId || !postId) { response.status(400).send({ message: 'Missing parameters', }); return; } response.status(200).send({ userId, postId, }); }; |
Cachowanie
W celu optymalizacji czasu wykonywania funkcji, możemy cachować zwracane odpowiedzi. Wymaga to ustawienia nagłówka Cache-Control. Vercel rekomenduje ustawienie dwóch konkretnych dyrektyw w nagłówku, tak aby kontrolować mechanizm w swojej infrastrukturze, wymuszając jednocześnie pominięcie zapamiętania odpowiedzi przez przeglądarkę. Prawidłową wartością powinno być
max-age=0 ,
s-maxage=86400
, gdzie 86400 to liczba sekund warunkująca na ile odpowiedź ma zostać cache’owana.
Dla naszej obecnej funkcji do zwracania wiadomości powitalnej, kod wyglądałby następująco:
1 2 3 4 5 6 7 8 9 10 11 |
import type { VercelRequest, VercelResponse } from '@vercel/node'; export default function (request: VercelRequest, response: VercelResponse) { const { name = 'World' } = request.query; response.setHeader('Cache-Control', 'max-age=0, s-maxage=86400'); response.status(200).send({ message: `Hello ${name}!`, }); }; |
Environment Variables
Podczas tworzenia API route’ów możemy potrzebować dodania dedykowanych zmiennych środowiskowych. Możemy je dodać w panelu lub przez komendę:
1 2 3 4 5 |
$ vercel env add FINANCE_API_KEY > Vercel CLI 28.16.12 > ? What’s the value of FINANCE_API_KEY? XXX > ? Add FINANCE_API_KEY to which Environments (select multiple)? Production, Preview, Development > ✅ Added Environment Variable FINANCE_API_KEY to Project ng-vercel-app [749ms] |
Przy dodawaniu envów możemy zaznaczyć do których środowisk klucz ma zostać zapisany. Jest to świetna opcja jeśli przykładowo posiadamy różne wersje serwisów, a klucze do nich różnią się od siebie w zależności od ich własnego środowiska.
Zmienne środowiskowe nie zostaną przekazane w procesie budowania angularowej aplikacji. Jeżeli chcielibyśmy je wykorzystać w aplikacji, możemy to zrobić przez dodatkową konfigurację webpacka, którą świetnie przedstawił nasz kolega Fanis na swoim kanale YT: https://www.youtube.com/watch?v=7ljEz52zdUM.
Jeżeli zdecydowaliśmy się na dodanie envów przez panel, możemy zaciągnąć je lokalnie poprzez komendę:
1 |
vercel env pull |
Bazy Danych
Vercel niedawno dodał integracje z ich własną usługą bazy danych. Dzięki temu, możemy w łatwy sposób uzyskać do niej dostęp w naszych API route’ach. Usługa Postgres jest obecnie w fazie Beta, więc od momentu publikacji artykułu może się wiele zmienić, np. cena usługi lubi API.
Po dodaniu bazy do naszego projektu automatycznie zostaną dodane credientale do environment variables.
W panelu możemy użyć query console do utworzenia pierwszej tabeli.
W ten sposób utworzyliśmy zbiornik dla naszych todos! Możemy teraz przejść do połączenia się z bazą w naszej aplikacji.
Dodajmy endpointa w pliku api/todos.ts do obsługi todosów:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
import { VercelRequest, VercelResponse } from "@vercel/node"; import { db } from "@vercel/postgres"; export default async function (request: VercelRequest, response: VercelResponse) { const client = await db.connect(); switch (request.method) { case 'GET': const todosQuery = await client.sql `SELECT * FROM todos;`; response.status(200).json({ todos: todosQuery.rows, }); break; case 'POST': const q = request.body; const createTodoQuery = await client.sql `INSERT INTO todos (description, is_done) VALUES (${q['description']}, false);`; response.status(200).json({ todos: createTodoQuery.rows, }); break; default: response.status(405).send({ message: `Method not supported`, }); } }; |
Nasz endpoint jest gotowy, teraz wystarczy, że obsłużymy go po stronie Angulara.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@Injectable() export class TodosService { private readonly _http = inject(HttpClient); getTodos(): Observable<Todo[]> { return this._http.get<{ todos: Todo[] }>('/api/todo').pipe( map((resp) => resp.todos) ); } addTodo(description: string): Observable<void> { return this._http.post<void>('/api/todo', { description }); } } |
W ten sposób stworzyliśmy full-stackową todo appkę!
Local production builds
Do tej pory używaliśmy komendy vercel dev w celu uruchomienia lokalnego serwera. Na pierwszy rzut oka może nas martwić brak parametrów konfiguracyjnych, domyślnie używanych przez Angular CLI, np. w komendzie
ng serve . Powiedzmy, że chcielibyśmy mieć możliwość podglądu produkcyjnego builda. Vercel umożliwia nam stworzenie pliku konfiguracyjnego, w którym będziemy mogli nadpisywać uruchamiane komendy.
W tym celu należy utworzyć plik vercel.json w głównym katalogu projektu, a następnie dodać odpowiedni wiersz z kluczem, który chcemy zmodyfikować. Uruchamianie serwera deweloperskiego to po prostu
devCommand . Musimy dodatkowo przekazać port narzucony przez Vercel CLI.
1 2 3 |
{ "devCommand": "ng serve --configuration production --port $PORT" } |
Po ponownym uruchomieniu komendy vercel dev , nasz config zostanie automatycznie wykryty wraz z nadpisanymi komendami. Nie jest to jednak najlepsze wyjście, ponieważ przełączanie między konfiguracjami wymagałoby każdorazowej zmiany zawartości pliku. Lepszą opcją będzie zmiana nazwy pliku, tak aby nie był automatycznie wykrywany, przykładowo na
vercel.prod.json . Plik ten możemy teraz przekazywać jako konfigurację przy uruchamianiu deva za pomocą argumentu
local-config .
1 |
vercel dev --local-config vercel.prod.json |
Podsumowanie
Vercel jest świetną opcją jako platforma do deployowania Angularowych aplikacji. Dostajemy wiele przydatnych funkcjonalności i integracji, przyjazny i prosty w obsłudze panel oraz automatyzacje procesu CD. Darmowy Hobby Plan zupełnie wystarcza do side projectów.
Poruszyliśmy jedynie kilka najważniejszych aspektów. Po pełen zakres wiedzy zachęcam do odwiedzenia dokumentacji https://vercel.com/docs
Zbudowana aplikacja jest dostępna w repozytorium na githubie pod linkiem https://github.com/Dyqmin/ng-vercel-app
Dodaj komentarz