Modern Frontend Setup: Configure an Astro Project
Overview
It is a good practice to config frontend projects with mordern tools like formatter, linter, testing to improve code quality. Here is how I set up an Astro project.
TypeScript
JavaScript continues to evolve through ECMAScript. ES6 was a major leap, modernizing the language, with features like let/const, arrow functions, classes, template literals, modules, and promises. Since then, every new version has added small but powerful features that make code easier, safer, and more expressive.
With TypeScript, you can write modern JavaScript (ES6+) while keeping type safety. TypeScript then compiles your code to a chosen ECMAScript version, ensuring it runs in the environments you need.
Astro has built-in TypeScript support and includes a tsconfig.json file, which tells the TypeScript compiler how to process your code. You can customize it for your project needs.
{
"extends": "astro/tsconfigs/strict",
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["dist"],
"compilerOptions": {
"target": "ES6",
"baseUrl": ".",
"paths": {
"@/assets/*": ["src/assets/*"],
"@/components/*": ["src/components/*"],
"@/layouts/*": ["src/layouts/*"],
"@/icons/*": ["src/icons/*"],
"@/i18n/*": ["src/i18n/*"],
"@/data/*": ["src/data/*"]
},
"strictNullChecks": true
}
}
The paths option creates aliases for your project directories.
import Button from "@/components/Button";
Prettier
Prettier is a code formatter that automatically formats code. It can be run from the command line (prettier --write) or used with the VSCode extension to automatically format code when saving code.
- install dependencies
npm install --save-dev --save-exact prettier prettier-plugin-astro
- create a .prettierrc configuration file
{
"plugins": [
"prettier-plugin-astro",
"prettier-plugin-tailwindcss" // needs to be last
],
"overrides": [
{
"files": "*.astro",
"options": {
"parser": "astro"
}
}
]
}
- add prettier command to package.json:
"scripts": {
"prettier": "prettier --write \"**/*.{js,jsx,ts,tsx,md,mdx,astro}\""
}
- install Prettier extension to enable automatic code formatting when saving files
ESLint
ESLint is a tool that analyzes code to enforce style, catch bugs, and automatically fix problems. It can be run from the command line (eslint --fix) or used with the VSCode extension to highlight and auto-fix problems as while coding.
- intsall dependencies
npm install --save-dev eslint eslint-plugin-astro @typescript-eslint/parser
- create config file: eslint.config.js
import eslintPluginAstro from "eslint-plugin-astro";
export default [
// add more generic rule sets here:
...eslintPluginAstro.configs.recommended,
{
rules: {
// override/add rules settings here:
"no-unused-vars": "warn",
},
},
];
- add lint command to package.json:
"scripts": {
"lint": "prettier --write \"**/*.{js,jsx,ts,tsx,md,mdx,astro}\" && eslint --fix \"src/**/*.{js,ts,jsx,tsx,astro}\""
}
-
install ESLint extension
-
update .vscode/setting.json
{
"eslint.validate": [
"javascript",
"javascriptreact",
"astro", // Enable .astro
"typescript", // Enable .ts
"typescriptreact" // Enable .tsx
]
}
See eslint-plugin-astro for more detail.
Test with Playwright
I used Playwright to write end to end test for the astro website. These tests verify that key user interactions — like navigating pages, checking content, and clicking buttons work as expected.
I used the Page Object Model pattern by creating a HomePage class. This separates page-specific operations from the test logic, making the code cleaner and easier to maintain.
Here’s an example test for the homepage:
import { test, expect } from "@playwright/test";
const headers = { "x-test-bypass": "true" };
test("homepage has title and intro text", async ({ browser }) => {
const context = await browser.newContext({
extraHTTPHeaders: headers,
});
const page = new HomePage(await context.newPage());
await page.goto("/");
await expect(page.title).resolves.toBe("Home");
await expect(page.heading).resolves.toContain("Hi, my name is Sherman Liu.");
await expect(page.footer).resolves.toContain(
"© 2025 Sherman Liu. All rights reserved."
);
// click button to go to About page
await page.clickAboutButton();
// verify navigation to about page
await expect(page.title).resolves.toBe("About");
await expect(page.heading).resolves.toContain("About Me");
});
test("responsive behaviour", async ({ browser }) => {
const context = await browser.newContext({
extraHTTPHeaders: headers,
});
const page = new HomePage(await context.newPage());
await page.goto("/");
await expect(page.hamburger).toBeHidden();
await page.showInMobile();
await expect(page.hamburger).toBeVisible();
await page.hamburger.click();
await expect(page.nav).toBeVisible();
const navLinks = page.nav.locator("a");
await expect(navLinks).toHaveCount(5);
});
test("switch language", async ({ browser }) => {
const context = await browser.newContext({
extraHTTPHeaders: headers,
});
const page = new HomePage(await context.newPage());
await page.goto("/");
await expect(page.title).resolves.toBe("Home");
await page.clickLanguageButton();
await expect(page.title).resolves.toBe("首页");
await expect(page.heading).resolves.toContain("Hi,我是 Sherman Liu。");
await expect(page.footer).resolves.toContain("版权所有");
await expect(page.getLink("简历")).resolves.toBeVisible();
});
class HomePage {
constructor(private page: any) {}
async goto(path: string) {
await this.page.goto(path);
}
get hamburger() {
return this.page.locator('button[aria-label="Menu"]');
}
get nav() {
return this.page.locator("#navigation");
}
get title() {
return this.page.title();
}
get heading() {
return this.page.locator("h1").first().textContent();
}
get footer() {
return this.page.locator("footer").textContent();
}
async getLink(name: string) {
return this.page.getByRole("link", { name }).first();
}
async getButton(name: string) {
return this.page.getByRole("button", { name }).first();
}
async clickAboutButton() {
const link = await this.getLink("About Me");
await link.click();
}
async clickLanguageButton() {
const button = await this.getButton("中文");
await button.click();
}
async showInMobile() {
await this.page.setViewportSize({ width: 375, height: 667 });
}
}
Metadata Setup
Storybook
TODO