🇬🇧 English
A website you write on your laptop. It goes on GitHub. Vercel puts it online, and does it again every time you push.
Astro is the tool that turns your files into a website. It sends no JavaScript unless you ask for some. Pages are small and open fast.
Eleven steps. You write every file yourself, one at a time, and run it after each one.
The last two steps do the thing a plain static host cannot: run your code when a visitor asks, not when you built the site.
🇹🇭 ไทย
เว็บไซต์ที่คุณเขียนบนแล็ปท็อป แล้วเอาขึ้น GitHub จากนั้น Vercel เอาขึ้นออนไลน์ให้ และทำซ้ำทุกครั้งที่คุณ push
Astro คือเครื่องมือที่เปลี่ยนไฟล์ของคุณให้เป็นเว็บไซต์ มันไม่ส่ง JavaScript ไปเลยถ้าคุณไม่ขอ หน้าเว็บจึงเล็กและเปิดเร็ว
สิบเอ็ดขั้น คุณเขียนทุกไฟล์เอง ทีละไฟล์ และรันดูหลังจบแต่ละขั้น
สองขั้นสุดท้ายทำสิ่งที่โฮสต์แบบสแตติกธรรมดาทำไม่ได้ คือรันโค้ดของคุณตอนที่ผู้เข้าชมถาม ไม่ใช่ตอนที่คุณสร้างเว็บ
🇨🇳 中文
一个你在笔记本上写的网站。放到 GitHub 上,Vercel 把它发布到线上,而且你每次 push 它都会再发布一次。
Astro 是把你的文件变成网站的工具。你不要求,它就一点 JavaScript 都不发。所以页面很小,打开很快。
十一步。每个文件都你自己写,一次一个,每写完一个就跑一次。
最后两步做的事,普通静态托管做不到:在访客提问的时候运行你的代码,而不是在你建站的时候。
| ('What you need', 'ต้องมีอะไร', '需要什么') | ('Why', 'ทำไม', '为什么') |
|---|---|
node 20 or newer | Astro runs on itAstro ทำงานบนมันAstro 跑在它上面 |
| A GitHub accountบัญชี GitHub一个 GitHub 账户 | where the code livesที่เก็บโค้ด代码放在那里 |
| A Vercel accountบัญชี Vercel一个 Vercel 账户 | free, and signs in with GitHubฟรี และล็อกอินด้วย GitHub ได้免费,用 GitHub 登录 |
💻 Tutorial 1 — A site on your own machine · บทที่ 1 — เว็บไซต์บนเครื่องของคุณเอง · 教程 1 —— 先在自己电脑上做一个网站
🇬🇧 English
Nothing is online yet. Everything here happens on your laptop, and you can see it in a browser while you type.
🇹🇭 ไทย
ยังไม่มีอะไรขึ้นออนไลน์ ทุกอย่างในบทนี้เกิดบนแล็ปท็อปของคุณ และคุณเห็นผลในเบราว์เซอร์ได้ระหว่างพิมพ์
🇨🇳 中文
现在还什么都没上线。这一节的一切都发生在你的笔记本上,你可以一边写一边在浏览器里看。
1 Make it, and run it · สร้างมัน แล้วรันมัน · 把它建出来,然后跑起来
🇬🇧 English
One command makes the whole project. It asks a few questions, and --template minimal answers most of them.
npm run dev starts a small web server on your own machine. Leave it running. It reloads as you save.
🇹🇭 ไทย
คำสั่งเดียวสร้างโปรเจกต์ทั้งก้อน มันจะถามไม่กี่ข้อ และ --template minimal ตอบให้เกือบหมดแล้ว
npm run dev เปิดเว็บเซิร์ฟเวอร์เล็ก ๆ บนเครื่องคุณเอง ปล่อยให้มันรันไว้ มันจะโหลดใหม่ทุกครั้งที่คุณเซฟ
🇨🇳 中文
一条命令就把整个项目建好。它会问几个问题,--template minimal 已经替你答了大部分。
npm run dev 在你自己机器上开一个小的网页服务器。让它一直跑着,你每次保存它都会重新加载。
⌨️ TERMINAL · make the project
npm create astro@latest my-site -- --template minimal
cd my-site
npm run dev
🖥️ WHAT IT PRINTED · a real run
Dev server running at http://localhost:4321
| ('Folder', 'โฟลเดอร์', '文件夹') | ('What goes in it', 'ใส่อะไรไว้', '放什么') |
|---|---|
src/pages/ | one file becomes one URLหนึ่งไฟล์กลายเป็นหนึ่ง URL一个文件就是一个网址 |
src/layouts/ | the frame around a pageกรอบที่ล้อมหน้าเว็บ包在页面外面的框 |
src/components/ | small pieces you reuseชิ้นเล็ก ๆ ที่ใช้ซ้ำ可以重复用的小块 |
public/ | files copied as they areไฟล์ที่คัดลอกไปตามเดิม原样复制过去的文件 |
astro.config.mjs | settings for the whole siteตั้งค่าของทั้งเว็บ整个网站的设置 |
- Run the three commands above.
- Open
http://localhost:4321. You should see a plain page. - Check: the terminal keeps running. That is correct.
- Leave it open. Every step below reloads in that browser tab.
- รันสามคำสั่งข้างบน
- เปิด
http://localhost:4321ควรเห็นหน้าเว็บเปล่า ๆ - ตรวจ: เทอร์มินัลยังรันค้างอยู่ นั่นถูกแล้ว
- เปิดทิ้งไว้ ทุกขั้นข้างล่างจะรีโหลดในแท็บนั้น
- 把上面三条命令跑一遍。
- 打开
http://localhost:4321,应该看到一个很朴素的页面。 - 检查:终端一直在跑,这是对的。
- 别关它。下面每一步都会在那个标签页里自动刷新。
2 A page is a file · หน้าเว็บคือไฟล์หนึ่งไฟล์ · 一个页面就是一个文件
🇬🇧 English
Make this file and the address /hello exists. You do not register it anywhere. The folder is the map.
The two lines of dashes at the top matter. Code between them runs while the page is being made, on your machine.
Below the dashes it is HTML, with one addition: curly brackets put a value into the page.
🇹🇭 ไทย
สร้างไฟล์นี้ แล้วที่อยู่ /hello ก็มีขึ้นมาเลย คุณไม่ต้องไปลงทะเบียนที่ไหน โครงโฟลเดอร์คือแผนที่
เส้นประสองบรรทัดบนสุดสำคัญ โค้ดที่อยู่ระหว่างมันจะรันตอนสร้างหน้าเว็บ บนเครื่องของคุณ
ใต้เส้นประลงมาคือ HTML ธรรมดา เพิ่มมาอย่างเดียวคือปีกกา ที่เอาค่ามาใส่ในหน้าเว็บ
🇨🇳 中文
建好这个文件,/hello 这个网址就存在了。你不用去哪里登记,文件夹结构就是地图。
最上面那两行破折号很重要。夹在中间的代码,会在生成页面的时候、在你的机器上运行。
破折号下面就是 HTML,只多了一样东西:大括号,把一个值放进页面里。
📄 NEW FILE · src/pages/hello.astro
---
// A page with nothing clever in it. No layout, no component, no imports.
// The point of this one is only that the file exists and the URL works.
const town = "Chiang Mai";
---
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Hello</title>
</head>
<body>
<h1>Hello from {town}</h1>
<p>This file is src/pages/hello.astro, and it is at /hello.</p>
</body>
</html>
| Line | What it does · ทำอะไร · 做什么 |
|---|---|
| --- | The fence. Code above the second one runs at build time. Never in a browser.เส้นรั้ว โค้ดเหนือเส้นที่สองรันตอนสร้าง ไม่เคยรันในเบราว์เซอร์栅栏。第二条之上的代码在构建时运行,永远不在浏览器里。 |
| const town = "Chiang Mai"; | Ordinary JavaScript. It runs once, when the page is made.JavaScript ธรรมดา มันรันครั้งเดียว ตอนสร้างหน้าเว็บ普通的 JavaScript。只在生成页面时运行一次。 |
| <h1>Hello from {town}</h1> | Curly brackets drop the value in. The visitor sees the words, not the code.ปีกกาเอาค่ามาวางตรงนั้น ผู้เข้าชมเห็นแต่ข้อความ ไม่เห็นโค้ด大括号把值放进去。访客看到的是文字,不是代码。 |
src/pages/hello.astro becomes /hello. index.astro is special: it becomes the folder itself. So src/pages/index.astro is your front page.
src/pages/hello.astro กลายเป็น /hello ส่วน index.astro พิเศษหน่อย มันกลายเป็นตัวโฟลเดอร์เอง ดังนั้น src/pages/index.astro คือหน้าแรกของคุณ
src/pages/hello.astro 会变成 /hello。index.astro 比较特别,它就等于那个文件夹本身。所以 src/pages/index.astro 就是你的首页。
- Create the file. Do not restart anything.
- Open
http://localhost:4321/hello. - Change
townto your own town and save. Watch it change. - Rename the file to
about.astro. Where is it now?
- สร้างไฟล์นี้ ไม่ต้องรีสตาร์ตอะไรทั้งนั้น
- เปิด
http://localhost:4321/hello - เปลี่ยน
townเป็นเมืองของคุณแล้วเซฟ ดูมันเปลี่ยน - เปลี่ยนชื่อไฟล์เป็น
about.astroตอนนี้มันอยู่ที่ไหน
- 建好这个文件,什么都不用重启。
- 打开
http://localhost:4321/hello。 - 把
town改成你自己的城市再保存,看它变。 - 把文件改名成
about.astro,现在它在哪个网址?
🧱 Tutorial 2 — Pieces that fit together · บทที่ 2 — ชิ้นส่วนที่ประกอบกันได้ · 教程 2 —— 拼得起来的零件
🇬🇧 English
Two pages already share a header. Copy it into both and you will edit it twice, for ever.
So: a layout holds what every page shares, and a component holds a piece you use again and again.
🇹🇭 ไทย
ตอนนี้สองหน้ามีหัวเว็บเหมือนกันแล้ว ถ้าก๊อปใส่ทั้งสองหน้า คุณจะต้องแก้สองที่ไปตลอดชีวิต
ดังนั้น เลย์เอาต์เก็บสิ่งที่ทุกหน้ามีเหมือนกัน ส่วนคอมโพเนนต์เก็บชิ้นที่คุณใช้ซ้ำแล้วซ้ำอีก
🇨🇳 中文
现在两个页面已经有一样的页头了。复制到两边,你就要永远改两次。
所以:布局装所有页面共用的部分,组件装你一用再用的那一小块。
3 A layout, and the hole in it · เลย์เอาต์ กับช่องว่างในนั้น · 一个布局,和它中间的洞
🇬🇧 English
A layout is a page with a hole. <slot /> is the hole. Whatever a page puts inside the layout lands there.
Astro.props is how the layout receives things from the page, like the title.
The <style> block at the bottom only affects this file. Astro scopes it for you.
🇹🇭 ไทย
เลย์เอาต์คือหน้าเว็บที่มีช่องโหว่ <slot /> คือช่องนั้น อะไรที่หน้าเว็บใส่ไว้ข้างในเลย์เอาต์ ก็จะไปโผล่ตรงนั้น
Astro.props คือช่องทางที่เลย์เอาต์รับของจากหน้าเว็บ เช่นชื่อเรื่อง
บล็อก <style> ข้างล่างมีผลเฉพาะไฟล์นี้ Astro จำกัดขอบเขตให้เอง
🇨🇳 中文
布局就是一个中间挖了洞的页面,<slot /> 就是那个洞。页面放在布局里面的东西,都会落到那里。
Astro.props 是布局从页面接东西的方式,比如标题。
最下面的 <style> 只影响这个文件,Astro 自动帮你限定了范围。
📄 NEW FILE · src/layouts/Base.astro
---
// Everything between the two lines of dashes runs while the page is being
// built, on the computer doing the building. It never reaches the browser.
const { title } = Astro.props;
const year = new Date().getFullYear();
---
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>{title}</title>
</head>
<body>
<header>
<a href="/">Doi Suthep Coffee</a>
<a href="/lessons">Lessons</a>
</header>
<main>
<!-- Whatever the page puts inside <Base> lands here. -->
<slot />
</main>
<footer>Chiang Mai · {year}</footer>
</body>
</html>
<style>
body { font-family: system-ui, sans-serif; max-width: 34rem;
margin: 0 auto; padding: 2rem; }
header a { margin-right: 1rem; }
footer { margin-top: 3rem; color: #666; font-size: 0.9rem; }
</style>
| Line | What it does · ทำอะไร · 做什么 |
|---|---|
| const { title } = Astro.props; | Take the title the page handed over. Props are how pieces talk.รับชื่อเรื่องที่หน้าเว็บส่งมา props คือวิธีที่ชิ้นส่วนคุยกัน接住页面递过来的标题。props 就是零件之间说话的方式。 |
| <slot /> | The hole. The page's own content is poured in here.ช่องโหว่ เนื้อหาของหน้าเว็บจะถูกเทลงตรงนี้那个洞。页面自己的内容会倒进这里。 |
| <style> | These rules reach this file only. No class name can clash with another file.กฎเหล่านี้มีผลแค่ไฟล์นี้ ชื่อคลาสจะไม่ชนกับไฟล์อื่นเลย这些规则只作用于这个文件。类名不会和别的文件撞车。 |
- Create the folder
src/layoutsand the file. - Check: nothing changes yet. No page uses it.
- That is the next step.
- Read the file once more. Find the hole.
- สร้างโฟลเดอร์
src/layoutsแล้วสร้างไฟล์นี้ - ตรวจ: ยังไม่มีอะไรเปลี่ยน เพราะยังไม่มีหน้าไหนใช้มัน
- นั่นคือขั้นต่อไป
- อ่านไฟล์อีกรอบ หาช่องโหว่ให้เจอ
- 建
src/layouts文件夹和这个文件。 - 检查:现在还什么都没变,因为没有页面用它。
- 那是下一步的事。
- 再读一遍这个文件,把那个洞找出来。
4 A page that uses the layout · หน้าเว็บที่เรียกใช้เลย์เอาต์ · 一个用上布局的页面
🇬🇧 English
Import the layout, then wrap the page in it. Everything between the opening and closing tag goes into the hole.
This page is now three lines of its own, and shares a header with every other page.
🇹🇭 ไทย
import เลย์เอาต์เข้ามา แล้วห่อหน้าเว็บไว้ข้างใน ทุกอย่างระหว่างแท็กเปิดกับแท็กปิดจะไปลงในช่องโหว่
ตอนนี้หน้านี้เหลือเนื้อหาของตัวเองสามบรรทัด และใช้หัวเว็บร่วมกับทุกหน้า
🇨🇳 中文
把布局 import 进来,再把页面包在里面。开标签和闭标签之间的一切,都会落进那个洞。
现在这个页面自己只有三行,而且和所有页面共用一个页头。
📄 NEW FILE · src/pages/lessons.astro
---
import Base from "../layouts/Base.astro";
---
<Base title="Lessons">
<h1>Lessons</h1>
<p>This file is <code>src/pages/lessons.astro</code>.</p>
<p>It is at <code>/lessons</code>. Nobody told it to be. The folder is the map.</p>
</Base>
| Line | What it does · ทำอะไร · 做什么 |
|---|---|
| import Base from "../layouts/Base.astro"; | A relative path, from this file. Two dots mean the folder above.เส้นทางแบบสัมพัทธ์ นับจากไฟล์นี้ จุดสองจุดคือโฟลเดอร์ชั้นบน相对路径,从这个文件算起。两个点表示上一层文件夹。 |
| <Base title="Lessons"> | title here is the prop the layout was waiting for.title ตรงนี้คือ prop ที่เลย์เอาต์รออยู่这里的 title 就是布局在等的那个 prop。 |
- Create the file, then open
/lessons. - Check: the header and footer are there, and you did not write them.
- Change the title. Look at the browser tab.
- Make
hello.astrouse the layout too.
- สร้างไฟล์ แล้วเปิด
/lessons - ตรวจ: หัวและท้ายเว็บอยู่ครบ ทั้งที่คุณไม่ได้เขียนมันในหน้านี้
- เปลี่ยนชื่อเรื่องดู แล้วมองที่แท็บของเบราว์เซอร์
- ลองทำให้
hello.astroใช้เลย์เอาต์ด้วย
- 建好文件,然后打开
/lessons。 - 检查:页头和页脚都在,而它们不是你在这页写的。
- 改一下标题,看看浏览器标签。
- 让
hello.astro也用上这个布局。
5 A component that takes props · คอมโพเนนต์ที่รับ props · 一个会收 props 的组件
🇬🇧 English
A component is a small piece of page. It takes props, the way a function takes arguments.
This one draws one bag of coffee. Give it a name and a price and it draws that one. Use it ten times for ten bags.
🇹🇭 ไทย
คอมโพเนนต์คือชิ้นเล็ก ๆ ของหน้าเว็บ มันรับ props แบบเดียวกับที่ฟังก์ชันรับอาร์กิวเมนต์
อันนี้วาดกาแฟหนึ่งถุง ให้ชื่อกับราคาไป มันก็วาดถุงนั้น ใช้สิบครั้งก็ได้สิบถุง
🇨🇳 中文
组件就是页面的一小块。它收 props,就像函数收参数一样。
这个组件画一包咖啡。给它名字和价格,它就画那一包。用十次就是十包。
📄 NEW FILE · src/components/Card.astro
---
// A component takes props, the same way a function takes arguments.
const { name, price, note } = Astro.props;
---
<article>
<h2>{name}</h2>
<p class="price">{price} baht</p>
<p>{note}</p>
</article>
<style>
article { border: 1px solid #ddd; border-radius: 8px;
padding: 1rem; margin: 1rem 0; }
h2 { margin: 0 0 0.25rem; font-size: 1.1rem; }
.price { margin: 0 0 0.5rem; color: #166534; font-weight: 600; }
</style>
| Line | What it does · ทำอะไร · 做什么 |
|---|---|
| const { name, price, note } = Astro.props; | Three props, unpacked in one line. Same idea as the layout's title.สาม props แกะออกมาในบรรทัดเดียว หลักการเดียวกับ title ของเลย์เอาต์三个 props,一行拆开。和布局里的 title 是一回事。 |
| <p class="price">{price} baht</p> | A prop and some text, side by side. Brackets for the value only.prop กับข้อความอยู่ด้วยกัน ใส่ปีกกาเฉพาะตรงที่เป็นค่าprop 和文字并排。只有值才用大括号。 |
- Create the folder
src/componentsand the file. - Check: again nothing changes. Nothing uses it yet.
- Add a fourth prop of your own, like
roast. - The next step is where it gets used.
- สร้างโฟลเดอร์
src/componentsแล้วสร้างไฟล์นี้ - ตรวจ: ยังไม่มีอะไรเปลี่ยนอีกเช่นเคย เพราะยังไม่มีใครใช้
- ลองเพิ่ม prop ที่สี่ของคุณเอง เช่น
roast - ขั้นต่อไปคือตอนที่มันได้ถูกใช้
- 建
src/components文件夹和这个文件。 - 检查:又是什么都没变,因为还没人用它。
- 自己加第四个 prop,比如
roast。 - 下一步就是用上它的地方。
6 A list, and the front page · ลิสต์ กับหน้าแรก · 一个列表,和首页
🇬🇧 English
Here everything meets. A layout around the outside, a list in the frontmatter, and a component drawn once per item.
.map turns a list of three coffees into three cards. This is ordinary JavaScript, running at build time.
🇹🇭 ไทย
ตรงนี้ทุกอย่างมาเจอกัน เลย์เอาต์อยู่รอบนอก ลิสต์อยู่ใน frontmatter และคอมโพเนนต์ถูกวาดหนึ่งครั้งต่อหนึ่งรายการ
.map เปลี่ยนลิสต์กาแฟสามชนิดให้เป็นการ์ดสามใบ นี่คือ JavaScript ธรรมดา ที่รันตอนสร้างเว็บ
🇨🇳 中文
这里所有东西汇合:外面是布局,frontmatter 里是列表,组件按每一项画一次。
.map 把三种咖啡的列表变成三张卡片。这就是普通的 JavaScript,在构建时运行。
📄 NEW FILE · src/pages/index.astro
---
import Base from "../layouts/Base.astro";
import Card from "../components/Card.astro";
// Ordinary JavaScript. This runs once, when the site is built.
const coffees = [
{ name: "Doi Chaang", price: 180, note: "Grown on the mountain, washed." },
{ name: "Doi Tung", price: 165, note: "Softer. Good with milk." },
{ name: "Mae Salong", price: 150, note: "The cheap one we drink ourselves." },
];
---
<Base title="Doi Suthep Coffee">
<h1>Our coffee</h1>
<p>{coffees.length} bags, roasted in Chiang Mai.</p>
{coffees.map((coffee) => (
<Card name={coffee.name} price={coffee.price} note={coffee.note} />
))}
</Base>
| Line | What it does · ทำอะไร · 做什么 |
|---|---|
| const coffees = [ | The data lives in the page. Later it could come from a file or an API.ข้อมูลอยู่ในหน้าเว็บเลย ทีหลังจะมาจากไฟล์หรือจาก API ก็ได้数据就放在页面里。以后它可以来自文件或者某个 API。 |
| {coffees.map((coffee) => ( | One card for each coffee. The brackets say: put the result here.หนึ่งการ์ดต่อหนึ่งกาแฟ ปีกกาบอกว่า เอาผลลัพธ์มาวางตรงนี้每种咖啡一张卡片。大括号的意思是:把结果放这儿。 |
| <Card name={coffee.name} price={coffee.price} note={coffee.note} /> | Passing props. Brackets for values, quotes for plain text.การส่ง props ใส่ปีกกาเมื่อเป็นค่า ใส่เครื่องหมายคำพูดเมื่อเป็นข้อความธรรมดา传 props。值用大括号,纯文字用引号。 |
- Create the file and open
http://localhost:4321. - Check: three cards, and the count in the sentence above them.
- Add a fourth coffee to the list. The count changes by itself.
- Delete the
noteon one. What does the card do?
- สร้างไฟล์ แล้วเปิด
http://localhost:4321 - ตรวจ: ต้องมีสามการ์ด และตัวเลขในประโยคข้างบนต้องตรง
- เพิ่มกาแฟตัวที่สี่เข้าไปในลิสต์ ตัวเลขจะเปลี่ยนเอง
- ลบ
noteของอันหนึ่งออก การ์ดใบนั้นเป็นอย่างไร
- 建好文件,打开
http://localhost:4321。 - 检查:三张卡片,上面那句话里的数量也对得上。
- 往列表里加第四种咖啡,数量会自己变。
- 把其中一个的
note删掉,那张卡片会怎样?
7 Build it, and see what a visitor gets · สั่งบิลด์ แล้วดูว่าผู้เข้าชมได้อะไร · 构建一次,看看访客拿到什么
🇬🇧 English
npm run build makes the real thing. The dev server was for you; this is for visitors.
Then look inside dist/. Plain HTML files, and the numbers are the point of Astro.
🇹🇭 ไทย
npm run build สร้างของจริง เซิร์ฟเวอร์ dev มีไว้ให้คุณ ส่วนอันนี้มีไว้ให้ผู้เข้าชม
แล้วลองเปิดดูข้างใน dist/ เป็นไฟล์ HTML ธรรมดา และตัวเลขที่ได้คือเหตุผลที่คนใช้ Astro
🇨🇳 中文
npm run build 生成真正要发布的东西。dev 服务器是给你用的,这个是给访客的。
然后看看 dist/ 里面:就是普通的 HTML 文件,而那几个数字正是 Astro 的意义所在。
⌨️ TERMINAL
npm run build
🖥️ WHAT IT PRINTED · a real build
generating static routes
├─ /hello/index.html (+9ms)
├─ /lessons/index.html (+5ms)
├─ /index.html (+3ms)
Completed in 28ms.
3 page(s) built in 629ms
| ('Measured on the built front page', 'วัดจากหน้าแรกที่บิลด์ออกมา', '在构建出来的首页上量到的') | ('', '', '') |
|---|---|
| JavaScript sent to the browserJavaScript ที่ส่งไปให้เบราว์เซอร์发给浏览器的 JavaScript | 0 bytes |
<script> tags in the pageแท็ก <script> ในหน้า页面里的 <script> 标签 | 0 |
| The whole page, CSS includedทั้งหน้ารวม CSS แล้ว整页,含 CSS | 1,670 bytes |
.map and your const are JavaScript, and they ran. They ran here, while building, and only their output was sent. The visitor downloads sentences, not code. That is why the page is 1,670 bytes.
.map และ const ของคุณคือ JavaScript และมันรันจริง แต่มันรันตรงนี้ ตอนบิลด์ แล้วส่งไปแค่ผลลัพธ์ ผู้เข้าชมดาวน์โหลดประโยค ไม่ได้ดาวน์โหลดโค้ด นั่นคือเหตุผลที่หน้านี้หนัก 1,670 ไบต์
你的 .map 和 const 都是 JavaScript,它们确实运行了。但它们是在这里、在构建时运行的,发出去的只有结果。访客下载的是句子,不是代码。所以这一页只有 1,670 字节。
- Run the build. Then open
dist/index.htmlin an editor. - Check: your coffee names are in there as plain text.
- Search that file for
<script. There is none. - Open
dist/index.htmlin a browser, straight from disk.
- สั่งบิลด์ แล้วเปิด
dist/index.htmlในโปรแกรมแก้ไขข้อความ - ตรวจ: ชื่อกาแฟของคุณอยู่ในนั้นเป็นข้อความธรรมดา
- ค้นหาคำว่า
<scriptในไฟล์นั้น จะไม่เจอเลย - เปิด
dist/index.htmlในเบราว์เซอร์ตรงจากดิสก์เลย
- 跑一次构建,然后用编辑器打开
dist/index.html。 - 检查:你的咖啡名字就在里面,是纯文字。
- 在那个文件里搜
<script,一个都没有。 - 直接从硬盘用浏览器打开
dist/index.html。
🚀 Tutorial 3 — GitHub, then Vercel · บทที่ 3 — GitHub แล้วค่อย Vercel · 教程 3 —— 先 GitHub,再 Vercel
🇬🇧 English
Now it goes online. GitHub keeps the code. Vercel watches GitHub.
You never upload anything again. You push, and the site changes.
🇹🇭 ไทย
ถึงเวลาขึ้นออนไลน์ GitHub เก็บโค้ด ส่วน Vercel คอยดู GitHub อยู่
คุณจะไม่ต้องอัปโหลดอะไรอีกเลย คุณแค่ push แล้วเว็บก็เปลี่ยน
🇨🇳 中文
现在上线。GitHub 存代码,Vercel 盯着 GitHub。
你再也不用上传任何东西。你 push,网站就变了。
8 Put it on GitHub · เอาขึ้น GitHub · 把它放到 GitHub 上
🇬🇧 English
Make an empty repository on github.com first. Do not let it add a README, or the first push will argue with you.
Then these six commands. The last one is the only one you will type again.
🇹🇭 ไทย
ไปสร้าง repository เปล่า ๆ บน github.com ก่อน อย่าให้มันใส่ README มาให้ ไม่งั้น push ครั้งแรกจะทะเลาะกับคุณ
แล้วใช้หกคำสั่งนี้ มีแค่คำสั่งสุดท้ายที่คุณจะได้พิมพ์อีกเรื่อย ๆ
🇨🇳 中文
先在 github.com 上建一个空仓库。别让它顺手加 README,不然第一次 push 会跟你吵架。
然后是这六条命令。只有最后一条,你以后还会一直打。
⌨️ TERMINAL · once per project
git init
git add .
git commit -m "my first astro site"
git branch -M main
git remote add origin https://github.com/YOURNAME/my-site.git
git push -u origin main
.gitignore for you, so git add . skips it. Check that the file is there before your first push. Vercel rebuilds those files itself from package.json.
โฟลเดอร์นั้นมีไฟล์เป็นพัน ๆ ที่คุณไม่ได้เขียนเอง Astro ใส่มันไว้ใน .gitignore ให้แล้ว git add . จึงข้ามมันไป ลองเช็กว่ามีไฟล์นั้นจริงก่อน push ครั้งแรก ส่วน Vercel จะสร้างไฟล์พวกนั้นขึ้นมาเองจาก package.json
那个文件夹里有成千上万个不是你写的文件。Astro 已经帮你把它写进 .gitignore 了,所以 git add . 会跳过它。第一次 push 之前确认那个文件在。Vercel 会自己根据 package.json 把那些文件装回来。
- Make the empty repository on github.com.
- Run the six commands.
- Check: refresh the GitHub page. Your files are there.
- Check:
node_modulesis not there.
- สร้าง repository เปล่าบน github.com
- รันหกคำสั่งนั้น
- ตรวจ: รีเฟรชหน้า GitHub ไฟล์ของคุณต้องอยู่ที่นั่น
- ตรวจ: ต้องไม่มี
node_modulesอยู่ด้วย
- 在 github.com 上建那个空仓库。
- 把六条命令跑完。
- 检查:刷新 GitHub 页面,你的文件都在。
- 检查:
node_modules不在里面。
9 Vercel deploys every push · Vercel เอาขึ้นให้ทุกครั้งที่ push · 你每次 push,Vercel 就发布一次
🇬🇧 English
This part is clicking, not typing. Sign in to vercel.com with GitHub, choose Add New Project, and pick the repository.
Vercel reads package.json, sees Astro, and fills the settings in. Change nothing. Press Deploy.
About a minute later you have an address ending in .vercel.app. That is the site.
🇹🇭 ไทย
ตอนนี้เป็นการคลิก ไม่ใช่การพิมพ์ ล็อกอิน vercel.com ด้วย GitHub เลือก Add New Project แล้วเลือก repository ของคุณ
Vercel อ่าน package.json เห็นว่าเป็น Astro แล้วกรอกค่าให้เอง ไม่ต้องแก้อะไร กด Deploy
อีกประมาณหนึ่งนาที คุณจะได้ที่อยู่ที่ลงท้ายด้วย .vercel.app นั่นแหละคือเว็บของคุณ
🇨🇳 中文
这一步是点鼠标,不是打字。用 GitHub 登录 vercel.com,选 Add New Project,挑你那个仓库。
Vercel 会读 package.json,认出是 Astro,然后自动把设置填好。什么都别改,按 Deploy。
大约一分钟后,你会拿到一个 .vercel.app 结尾的地址。那就是你的网站。
| ('Vercel works this out', 'Vercel เดาให้เอง', 'Vercel 自己判断出来的') | ('And it is right', 'และมันถูกแล้ว', '而且是对的') |
|---|---|
| Frameworkเฟรมเวิร์ก框架 | Astro |
| Build commandคำสั่งบิลด์构建命令 | npm run build |
| Output folderโฟลเดอร์ผลลัพธ์输出文件夹 | dist |
| Install commandคำสั่งติดตั้ง安装命令 | npm install |
main and the live site changes. Push any other branch and Vercel builds it at a separate address. Nobody else will find it. Open a pull request and Vercel writes that address in a comment. Try the change before you merge it.
push ขึ้น main เว็บจริงก็เปลี่ยน แต่ถ้า push ขึ้นบรานช์อื่น Vercel จะบิลด์ให้ที่อยู่แยกต่างหาก ซึ่งคนอื่นหาไม่เจอ พอเปิด pull request Vercel จะเขียนที่อยู่นั้นไว้ในคอมเมนต์ ลองของจริงก่อนจะ merge ได้เลย
push 到 main,线上网站就变。push 到别的分支,Vercel 会在另一个地址上单独构建,别人找不到。开一个 pull request,Vercel 会把那个地址写在评论里。合并之前,先把改动点开看看。
- Connect the repository and press Deploy.
- Check: open the
.vercel.appaddress. - Change a coffee price on your laptop. Commit and push.
- Check: wait a minute, reload. No uploading anywhere.
- เชื่อม repository แล้วกด Deploy
- ตรวจ: เปิดที่อยู่
.vercel.appดู - แก้ราคากาแฟสักอันบนแล็ปท็อป แล้ว commit และ push
- ตรวจ: รอสักนาทีแล้วรีโหลด ไม่ต้องอัปโหลดอะไรเลย
- 连上仓库,按 Deploy。
- 检查:打开那个
.vercel.app地址。 - 在笔记本上改一个咖啡价格,commit 然后 push。
- 检查:等一分钟再刷新。你没有往任何地方上传东西。
⚡ Tutorial 4 — The thing a static host cannot do · บทที่ 4 — สิ่งที่โฮสต์สแตติกทำไม่ได้ · 教程 4 —— 静态托管做不到的那件事
🇬🇧 English
Every page so far was decided when you built it. That is fast, and it is also the limit.
A static page cannot know the time, read a database, or keep a secret. Ask it whether the shop is open and it answers with this morning.
Vercel can run one file per visit instead. That is the whole of the difference, and it is two steps.
🇹🇭 ไทย
ทุกหน้าที่ผ่านมาถูกตัดสินตั้งแต่ตอนคุณบิลด์ ซึ่งเร็วมาก และก็เป็นข้อจำกัดของมันด้วย
หน้าสแตติกไม่รู้เวลา อ่านฐานข้อมูลไม่ได้ และเก็บความลับไม่ได้ ถ้าถามว่าร้านเปิดไหม มันจะตอบด้วยข้อมูลของเมื่อเช้า
แต่ Vercel รันไฟล์หนึ่งไฟล์ต่อการเข้าชมหนึ่งครั้งได้ นั่นคือความต่างทั้งหมด และใช้แค่สองขั้น
🇨🇳 中文
到目前为止每个页面,内容都是构建那一刻就定下的。这很快,也是它的上限。
静态页面不知道现在几点,读不了数据库,也藏不住密钥。问它店开没开,它会拿今天早上的答案回你。
而 Vercel 可以改成每次访问跑一个文件。差别全在这里,只要两步。
10 An adapter, and a route that runs per visit · อะแดปเตอร์ กับเส้นทางที่รันทุกครั้งที่มีคนเข้า · 一个适配器,和一条每次访问都运行的路由
🇬🇧 English
One command adds the adapter and edits the config for you. The adapter teaches Astro how to package code for Vercel.
Then a file under src/pages/api/ becomes an address that returns data instead of a page.
The important line is prerender = false. Without it, Astro answers the question once, at build time, for ever.
🇹🇭 ไทย
คำสั่งเดียวติดตั้งอะแดปเตอร์และแก้ไฟล์ตั้งค่าให้เสร็จ อะแดปเตอร์สอน Astro ว่าจะแพ็กโค้ดให้ Vercel อย่างไร
จากนั้นไฟล์ใน src/pages/api/ จะกลายเป็นที่อยู่ ที่คืนข้อมูลกลับมา แทนที่จะเป็นหน้าเว็บ
บรรทัดสำคัญคือ prerender = false ถ้าไม่มีมัน Astro จะตอบคำถามนี้ครั้งเดียวตอนบิลด์ แล้วใช้คำตอบนั้นไปตลอด
🇨🇳 中文
一条命令就装好适配器,还顺手改好配置。适配器教 Astro 怎么把代码打包给 Vercel。
然后 src/pages/api/ 下面的文件,就变成一个返回数据而不是页面的地址。
关键那行是 prerender = false。没有它,Astro 只会在构建时回答一次,然后永远用那个答案。
⌨️ TERMINAL · it edits astro.config.mjs for you
npx astro add vercel
📄 CHANGED FILE · astro.config.mjs
// @ts-check
import { defineConfig } from 'astro/config';
import vercel from '@astrojs/vercel';
// https://astro.build/config
export default defineConfig({
adapter: vercel()
});
📄 NEW FILE · src/pages/api/open.ts
import type { APIRoute } from "astro";
// Without this line Astro answers the question once, while building, and
// serves that same answer for ever. The shop would be open all night.
export const prerender = false;
const OPENS = 7;
const CLOSES = 18;
export const GET: APIRoute = async () => {
// Bangkok time, whatever time zone the server happens to be in.
const now = new Date();
const bangkok = new Date(now.toLocaleString("en-US", {
timeZone: "Asia/Bangkok",
}));
const hour = bangkok.getHours();
return new Response(
JSON.stringify({
open: hour >= OPENS && hour < CLOSES,
hour,
opens: OPENS,
closes: CLOSES,
asked: now.toISOString(),
}),
{ headers: { "Content-Type": "application/json" } },
);
};
| Line | What it does · ทำอะไร · 做什么 |
|---|---|
| export const prerender = false; | Run this every visit, not once. The one line that matters here.ให้รันทุกครั้งที่มีคนเข้า ไม่ใช่ครั้งเดียว บรรทัดเดียวที่สำคัญที่สุดตรงนี้每次访问都跑,不是只跑一次。这里最要紧的就是这一行。 |
| export const GET: APIRoute = async () => { | The name is the method. GET answers an ordinary browser visit.ชื่อคือเมธอด GET ตอบการเข้าชมแบบธรรมดาของเบราว์เซอร์名字就是方法。GET 回应浏览器的普通访问。 |
| timeZone: "Asia/Bangkok", | The server is somewhere else. Say which clock you mean.เซิร์ฟเวอร์อยู่ที่อื่น ต้องบอกให้ชัดว่าใช้นาฬิกาไหน服务器在别的地方。说清楚你要哪个时区的钟。 |
| return new Response( | Plain web standards. No Vercel-only code anywhere in this file.มาตรฐานเว็บล้วน ๆ ไม่มีโค้ดเฉพาะของ Vercel ในไฟล์นี้เลย就是标准的 Web API。这个文件里没有任何只属于 Vercel 的代码。 |
🖥️ TWO REAL CALLS · two seconds apart
curl http://localhost:4321/api/open
{"open":true,"hour":17,"opens":7,"closes":18,"asked":"2026-09-22T10:26:14.642Z"}
curl http://localhost:4321/api/open
{"open":true,"hour":17,"opens":7,"closes":18,"asked":"2026-09-22T10:26:16.814Z"}
prerender = false, build, and watch the time freeze.
คำตอบสองอันนั้นไม่เหมือนกัน หน้าที่บิลด์ไว้แล้วทำแบบนี้ไม่ได้ มันจะพกเวลาเดิมไปจนกว่าคุณจะบิลด์ใหม่ ลองเอา prerender = false ออก แล้วบิลด์ดู เวลาจะค้างอยู่กับที่
那两个答案不一样。构建好的页面做不到这个:它会一直带着同一个时间戳,直到你重新构建。把 prerender = false 拿掉再构建一次,看时间怎么冻住。
- Run
npx astro add verceland say yes to everything. - Create the api folder and the file.
- Open
http://localhost:4321/api/open. Reload it twice. - Check:
askedchanges every time.
- รัน
npx astro add vercelแล้วตอบ yes ทุกข้อ - สร้างโฟลเดอร์ api แล้วสร้างไฟล์นี้
- เปิด
http://localhost:4321/api/openแล้วรีโหลดสองครั้ง - ตรวจ: ค่า
askedต้องเปลี่ยนทุกครั้ง
- 跑
npx astro add vercel,一路 yes。 - 建 api 文件夹和那个文件。
- 打开
http://localhost:4321/api/open,刷新两次。 - 检查:
asked每次都不一样。
11 Read what the build made · อ่านสิ่งที่การบิลด์สร้างออกมา · 读懂构建生成了什么
🇬🇧 English
Build it again and the wording changes. It now says prerendering, and it bundles a function at the end.
Look in .vercel/output/ and you can see the bargain Astro struck for you.
Three pages are files, served from a CDN, costing nothing to run. One address wakes a function. Only that one.
🇹🇭 ไทย
บิลด์อีกครั้ง แล้วคำที่ขึ้นจะเปลี่ยนไป คราวนี้มันบอกว่า prerendering และตอนท้ายมันแพ็กฟังก์ชันขึ้นมาด้วย
ลองดูใน .vercel/output/ แล้วคุณจะเห็นข้อตกลงที่ Astro จัดการให้คุณ
สามหน้าเป็นไฟล์ เสิร์ฟจาก CDN ไม่มีค่ารัน ส่วนอีกหนึ่งที่อยู่จะปลุกฟังก์ชันขึ้นมา แค่อันเดียวนั้น
🇨🇳 中文
再构建一次,用词变了。它现在说 prerendering,最后还打包了一个函数。
看看 .vercel/output/,你就能看到 Astro 替你做的那笔交易。
三个页面是文件,从 CDN 发出去,跑起来不花钱。只有一个地址会唤醒函数,就那一个。
🖥️ THE SAME BUILD, AFTER THE ADAPTER
prerendering static routes
├─ /hello/index.html (+9ms)
├─ /lessons/index.html (+5ms)
├─ /index.html (+3ms)
[@astrojs/vercel] Bundling function ../../server/entry.mjs
[@astrojs/vercel] Copying static files to .vercel/output/static
Server built in 1.94s
🖥️ WHAT VERCEL ACTUALLY RECEIVES
.vercel/output/static/index.html
.vercel/output/static/hello/index.html
.vercel/output/static/lessons/index.html
.vercel/output/functions/_render.func
🖥️ AND HOW IT DECIDES · from .vercel/output/config.json
{ "handle": "filesystem" }
{ "src": "^/api/open/?$", "dest": "_render" }
{ "src": "^/.*$", "dest": "_render", "status": 404 }
handle: filesystem comes first, so a real file always wins. Your three pages never touch the function. Only an address with no file behind it falls through to it. You pay for the one route that needs it.
handle: filesystem มาก่อน ไฟล์จริงจึงชนะเสมอ สามหน้าของคุณไม่เคยแตะฟังก์ชันเลย มีแต่ที่อยู่ที่ไม่มีไฟล์รองรับเท่านั้นที่จะตกลงไปหามัน คุณจ่ายเฉพาะเส้นทางเดียวที่ต้องใช้จริง
handle: filesystem 排在最前面,所以真实文件永远优先。你那三个页面根本碰不到函数。只有背后没有文件的地址才会掉下去找它。你只为真正需要的那一条路由付钱。
- Run
npm run buildagain. Read the last four lines. - Check: it says prerendering, not generating.
- Look inside
.vercel/output/static. Your pages are files. - Push it. The live site now has a working
/api/open.
- รัน
npm run buildอีกครั้ง แล้วอ่านสี่บรรทัดสุดท้าย - ตรวจ: ต้องขึ้นว่า prerendering ไม่ใช่ generating
- เปิดดูใน
.vercel/output/staticหน้าเว็บของคุณเป็นไฟล์ - push ขึ้นไป ตอนนี้เว็บจริงมี
/api/openที่ใช้งานได้แล้ว
- 再跑一次
npm run build,读最后四行。 - 检查:它说的是 prerendering,不是 generating。
- 看看
.vercel/output/static里面,你的页面都是文件。 - push 上去。线上网站现在有一个能用的
/api/open了。
🇬🇧 English
Eleven steps, and a site that rebuilds itself whenever you push.
What to take away is the split. Almost everything can be decided once, while building, and then it is only a file on a CDN.
A few things cannot. Those get a function, and you mark them one at a time with a single line.
🇹🇭 ไทย
สิบเอ็ดขั้น และเว็บไซต์ที่สร้างตัวเองใหม่ทุกครั้งที่คุณ push
สิ่งที่ควรจำคือการแบ่ง เกือบทุกอย่างตัดสินครั้งเดียวตอนบิลด์ได้ แล้วมันก็เป็นแค่ไฟล์บน CDN
มีไม่กี่อย่างที่ทำแบบนั้นไม่ได้ พวกนั้นได้ฟังก์ชันไป และคุณทำเครื่องหมายทีละอันด้วยโค้ดบรรทัดเดียว
🇨🇳 中文
十一步,换来一个你一 push 就自己重建的网站。
该记住的是那个划分:绝大多数东西都可以在构建时定下来,之后它就只是 CDN 上的一个文件。
少数东西不行。那些拿到一个函数,而你用一行代码,一个一个把它们标出来。
| ('You want', 'คุณอยากได้', '你想要') | ('Look up', 'ไปหาคำว่า', '去查') |
|---|---|
| Blog posts from filesบทความจากไฟล์用文件写博客文章 | content collections |
| A button that does somethingปุ่มที่กดแล้วทำงาน一个按下去有反应的按钮 | client:load |
| A password Vercel keepsรหัสลับที่ Vercel เก็บให้让 Vercel 保管的密钥 | environment variablesenvironment variables环境变量 |
| Your own domain nameชื่อโดเมนของคุณเอง你自己的域名 | Vercel → Settings → DomainsVercel → Settings → DomainsVercel → Settings → Domains |