This document contains my coding guidelines and best practices for software projects. It is meant to be general purpose and should be a starting point. If any guidelines here conflict with a project's specific guidelines, then the project's specific guidelines should take precedence.
Write secure code.
frontend folder at the root of the project
<head> since it's usually shared across pages) and then the body of the page be a LIT component that will be hydrated on the client-sidebackend folder at the root of the project
scripts folder at the root of the project
/medication/ instead of /medication)window.location.href, redirects), always include the trailing slash before query parameters/medication/?id=123 instead of /medication?id=123 to avoid 301 redirects that strip query parameters<!-- frontend/_components/page-head.webc -->
<head webc:is="fw-head" webc:nokeep>
<meta charset="UTF-8" />
<title @text="title"></title>
<link rel="icon" href="/logo.svg" />
<link rel="apple-touch-icon" href="/logo-180.png" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, viewport-fit=cover"
/>
<meta name="description" :content="description" />
<link rel="manifest" href="/app.webmanifest" />
<!-- Control the behavior of search engine crawling and indexing -->
<!-- All Search Engines -->
<meta name="robots" content="index,follow" />
<!-- Helps prevent duplicate content issues -->
<link rel="canonical" :href="canonical" />
<!-- Facebook Open Graph meta -->
<meta property="og:url" :content="canonical" />
<meta property="og:type" content="website" />
<meta property="og:title" :content="title" />
<meta property="og:image" :content="og_image" />
<meta property="og:description" :content="description" />
<meta property="og:site_name" :content="title" />
<meta property="og:locale" content="en_US" />
<script
src="https://cdn.usefathom.com/script.js"
defer
:data-site="fathom_site_id"
webc:keep
></script>
<script type="module">
navigator.serviceWorker.register("/sw.js");
</script>
</head>
// frontend/_data/site.js
const url = "https://app-url-here.com";
export default {
title: "My App",
description: "My App description here",
url,
canonicalUrl: `${url}/`,
copyrightName: "My App",
isProdEnv: process.env.ELEVENTY_ENV === "production",
fathom_site_id: "my-fathom-site-id-here",
currentYear: new Date().getFullYear(),
build_time: new Date().toISOString(),
};
<!-- frontend/includes/layout.webc -->
<html lang="en">
<head>
<meta
webc:is="page-head"
:title="page.title || site.title"
:description="page.description || site.description"
:fathom_site_id="site.fathom_site_id"
:canonical="site.url + page.url"
/>
<!-- inline the webc component styles -->
<style @raw="getBundle('css')" webc:keep></style>
<!-- inline the webc component js -->
<script type="module" @raw="getBundle('js')" webc:keep></script>
<!-- other stuff -->
</head>
</html>
We will use cloudflare workers for hosting. The frontend will be static assets served from cloudflare workers, and the backend will be serverless functions served from cloudflare workers.
if you can build something with HTML and CSS, favor doing that over using javascript
use progressive enhancements as often as possible
Use modern CSS for styling (no preprocessors, no frameworks, no css-in-js)
Use grid and flexbox for layout
use semantic elements when possible, only use divs and spans when there is no other semantically-appropriate element. Avoid semanticless divs - prefer elements like <header>, <nav>, <main>, <article>, <section>, <aside>, <footer>, <form>, etc. when they semantically match the content.
when using input elements, always wrap them in a <form> element for proper semantics and accessibility. This ensures proper form submission behavior, keyboard navigation, and screen reader support.
prefer nested css selectors, element and ID selectors over class selectors. For example, instead of <header><h1 class="title">The title</h1></header>, use <header><h1>The title</h1></header> and then use a selector like header { & h1 { ... } } instead of .title { ... }. Because if we are writing accessible, semantic HTML then there is only one h1 in the header.
Page structure pattern: Most pages should wrap their content in a <main> element, and nest all CSS rules under main using the & prefix. Use element selectors and IDs (for unique elements) rather than classes. Only use classes when an element appears multiple times or styling is meant to be reusable. Example:
<main>
<header>
<h1>Page Title</h1>
</header>
<section id="content">
<p>Content here</p>
</section>
</main>
main {
max-width: 800px;
margin: 0 auto;
& header {
margin-bottom: 2rem;
& h1 {
font-size: 2rem;
}
}
& #content {
line-height: 1.6;
}
}
avoid inline styles in html, figure out a proper selector and use that to select and style the element.
For a card component: use an article element. And instead of using div class="footer" in the card,use a <footer> element. Same for the header, use a <header> element. For the body use a <section> element.
if you can't write a selector based on an element tag, you can use an ID if that element is unique inside the shadow root of that component.
use REM over pixels for font sizes and spacing.
// @ts-check at the top of the file to enable type-checking in .js files@typedef, @param, @returns, and @type annotations to express types in JSDoc@type {string[]}) when inference is not obvious.d.ts files. Data models are an example of a shared type.d.ts files into our code with jsdoc comments, like this:/** @import { User } from '../../models/User' */
when using lit as a dependency add it as a devDependency for typing at DX time, and use an import map "lit": "https://cdn.jsdelivr.net/gh/lit/dist@3.3.1/core/lit-core.min.js" to load it at runtime.
declare properties in the static properties object, and then set their default value and type in the class with field initializers, like this:
static properties = {
page_name: { type: String },
stuff: {type: Array<string>},
some_state {type: String, state: true}
};
/** @type {string} */
page_name = 'new/edit';
/** @type {string[]} */
stuff = [];
/** @type {string} */
some_state = 'initial';
export class MyThing extends LitElement {
// ...
}
if (!customElements.get("my-thing")) {
customElements.define("my-thing", MyThing);
}
# prefix for private class fields and methodsany type completely - never use any in production codeas unknown as T - this pattern indicates poor type designT[] instead of Array<T> - follow consistent array type syntaxType | null instead of {} as Typepages/list.js -> pages/list.ui.test.js).*.ui.test.js.await expect(page.getByRole('heading')).toContainText('New solution'); should be await expect(page.getByRole('heading', { name: "New solution" })).toBeVisible();.waitForTimeout in tests. Playwright has implicit waits built into all locator logic. Instead of arbitrary timeouts, wait for the expected UI state changes (e.g., wait for a specific element to appear, text to change, or button state to update). This makes tests more reliable and faster.This applies to both non-UI client-side code and backend code. This includes unit/functional tests and scenario-based tests.
node:test) for non-ui tests.*.test.js.filename.test.js.",tests-e2e/ folder at the highest common ancestor of the files being tested.import/export (ESM) syntax — no CommonJS.assert, assert.strictEqual, and similar functions from Node's built-in assert module.test() and describe() blocks from node:test.If there is a file sum.js with the following code:
/**
* @param {number} a
* @param {number} b
* @returns {number}
*/
export function sum(a, b) {
return a + b;
}
Then we want our test next to in the same directory, named sum.test.js with the following code:
import { test } from "node:test";
import assert from "node:assert";
import { sum } from "./sum.js";
test("sum", () => {
assert.strictEqual(sum(1, 2), 3);
});
__fixtures__ directory.const response_with_no_highlights = {...test_response_from_fixture, highlights: []}SolutionFilter)get_solutions)ListPage)MAX_SOLUTIONS)max_solutions)max_solutions)const and let over var.Only apply these guidelines to projects that are built with Eleventy.
<script webc:setup> and render with <template webc:root><script webc:keep> to tell webc to keep the script tag around after build-timerender method. Ideally try to avoid this by breaking down objects into scalar values and passing them as attributes._(this) function to access the props.<script webc:setup>
// @ts-check
/** @typedef Props
* @prop {string} name The name of the setting from settings.js
*/
/**
* @param {Props} props
*/
function _(props) {
// @ts-ignore - settings is available at build time
const setting = settings[props.name];
if (!setting) {
throw new Error(`Setting "${props.name}" not found in settings.js`);
}
return {
storage_key: setting.storage_key,
label: setting.label,
default_value: setting.default_value,
};
}
</script>
<fw-setting-toggle
:data-storage-key="_(this).storage_key"
:data-default-value="_(this).default_value"
:data-label="_(this).label"
>
<label>
<input
type="checkbox"
:data-storage-key="_(this).storage_key"
:checked="_(this).default_value"
/>
<span id="label" @text="_(this).label"></span>
</label>
</fw-setting-toggle>
Allowed types:
Examples:
feat(auth): add passwordless login
fix(cache): prevent stale responses
chore(deps): bump vite to 5.1
The PR title type should be inferred from the primary change:
always squash and merge PRs, use the PR title as the commit message and leave the body empty.
npm run format:fix to format the code before making a commit.