How to embed an excel data table in a next.js static site with tailwind css
- Step 1Keep reference data on the first sheet — Maintain pricing, features, or roster data in the spreadsheet with headers in row 1 on the first tab. The tool reads only sheet index 0 and uses row 1 as the
<th>labels. - Step 2Drop the .xlsx or .csv onto the tool — SheetJS parses it in your browser. Cells are read as formatted display text, so dates and formatted numbers export as the strings you see.
- Step 3Pick a style and optionally dark mode — Choose
striped,bordered,compact, orminimal, and toggle dark mode if your static site has a dark theme. - Step 4Choose your embed target: .tsx or .mdx — For an
.mdxpage, paste the HTML directly — MDX renders embedded HTML. For a.tsxpage, convertclass=toclassName=, or render the string viadangerouslySetInnerHTMLfor static, trusted content. - Step 5Add the file to your Tailwind content globs — Whichever file holds the table must be matched by your Tailwind
contentconfiguration, or the production build purges the utility classes and the table renders unstyled. - Step 6Rebuild to bake the table into the static output — On the next
next build, the table HTML is included in the pre-rendered page. Re-export and re-paste whenever the spreadsheet changes — there's no live data binding.
Embedding the snippet by file type
The output is HTML using the class attribute. How you embed depends on the file.
| File type | Paste approach | Note |
|---|---|---|
| .mdx | Paste the HTML as-is | MDX renders embedded HTML directly — no conversion needed |
| .tsx (editable JSX) | Replace class= with className= | Lets you edit the JSX later; manual find/replace |
| .tsx (static data) | dangerouslySetInnerHTML={{ __html }} | Trusted, build-time data only; cells already escaped by the tool |
| Server Component | Return the HTML via one of the above | App Router; still need className or dangerouslySetInnerHTML |
Build-time concerns for SSG
The two things that bite people embedding generated Tailwind tables in Next.js.
| Concern | Symptom | Fix |
|---|---|---|
| Tailwind content purge | Table renders with no styling in prod | Add the file path to your Tailwind content array, rebuild |
| class vs className | JSX build error / React warning | Convert class= to className=, or use dangerouslySetInnerHTML |
| Stale data after edit | Old numbers show after spreadsheet change | Re-export, re-paste, and rebuild — there's no live binding |
| Dark mode not applying | Table stays light in dark theme | Use Tailwind's class dark strategy and toggle dark on a parent |
Plan limits
Pro-tier feature; excel family limits.
| Plan | Max file size | Max rows |
|---|---|---|
| Free | Not available (Pro required) | — |
| Pro | 50 MB | 100,000 |
| Pro-media | 200 MB | 500,000 |
| Developer | 500 MB | Unlimited |
Cookbook
Embed patterns for a Next.js SSG workflow — MDX, .tsx, and the Tailwind content config you must not forget.
Paste straight into an .mdx page
MDX renders embedded HTML, so the tool output goes in unchanged. The table is baked into the static page at build.
// content/pricing.mdx
# Pricing
Here are our plans:
<div class="overflow-x-auto">
<table class="w-full text-sm text-left text-gray-700 border-collapse">
<thead><tr><th class="px-4 py-3 bg-gray-100 ...">Plan</th></tr></thead>
<tbody><tr class="even:bg-gray-50"><td class="px-4 py-3 border-b">Pro</td></tr></tbody>
</table>
</div>Embed in a .tsx page with className
Convert class to className for an editable JSX block inside a statically generated page.
// app/team/page.tsx (Server Component, statically rendered)
export default function TeamPage() {
return (
<main>
<div className="overflow-x-auto">
<table className="w-full text-sm text-left text-gray-700 border-collapse">
<thead><tr><th className="px-4 py-3 bg-gray-100 ...">Name</th></tr></thead>
<tbody><tr className="even:bg-gray-50"><td className="px-4 py-3 border-b">Ada</td></tr></tbody>
</table>
</div>
</main>
);
}Static embed with dangerouslySetInnerHTML
For build-time data you won't edit in JSX, inject the raw HTML string. Cells are already escaped by the tool.
// app/specs/page.tsx
const specsTable = `<div class="overflow-x-auto">...</div>`; // pasted from the tool
export default function SpecsPage() {
return <section dangerouslySetInnerHTML={{ __html: specsTable }} />;
}Tailwind content config so classes survive the build
The most common SSG mistake. If the file holding the table isn't matched by content globs, the classes get purged and the table renders unstyled in production.
// tailwind.config.js
module.exports = {
content: [
'./app/**/*.{js,ts,jsx,tsx,mdx}',
'./content/**/*.{md,mdx}', // <-- include MDX content
],
darkMode: 'class', // <-- needed for the dark: classes
// ...
};Dark-mode minimal table for a docs site
Minimal + dark mode, baked into a docs page. The dark classes land on the wrapper, th, and tr.
Config: style = minimal, darkMode = on
<div class="overflow-x-auto dark:bg-gray-800 dark:text-gray-100">
<table class="w-full text-sm text-left text-gray-700">
<thead><tr>
<th class="px-4 py-2 font-semibold text-gray-800 dark:bg-gray-800 dark:text-gray-100">Setting</th>
</tr></thead>
<tbody>
<tr class="dark:bg-gray-800 dark:text-gray-100">
<td class="px-4 py-2">cacheTtl</td>
</tr>
</tbody>
</table>
</div>Edge cases and what actually happens
Pasting class-based HTML into a .tsx return
JSX class errorThe output uses the HTML class attribute; JSX needs className. An unconverted paste throws a build error or warning. Convert class= to className=, or render the string via dangerouslySetInnerHTML. In .mdx files no conversion is needed.
Classes purged from the production build
Purge issueIf the page/MDX file holding the table isn't covered by your Tailwind content globs, the build strips the utility classes and the table renders unstyled in production (it often looks fine in dev). Add the file path to content and rebuild.
Expecting the table to update when the spreadsheet changes
No live bindingThe embed is a one-time paste of static HTML. Editing the spreadsheet does not update the page. Re-export, re-paste, and rebuild on every data change — or build a real data-fetching pipeline instead of inline HTML.
Dark mode classes don't apply on the static site
Config issueThe dark: variants require Tailwind's class dark-mode strategy (darkMode: 'class') and a dark class toggled on a parent such as <html>. Without that configuration the dark classes never activate.
Data on a second sheet
First sheet onlyOnly sheet index 0 is read. Move the reference data to the first tab, or export that tab as its own file before generating the table.
Row 1 holds a title, not headers
By designRow 1 always becomes the <th> cells. A title or note in row 1 becomes your headers and the real headers shift into the body. Delete leading non-header rows so row 1 is the genuine header.
Inlining a very large table into a static page
Heavy payloadEvery row becomes a <tr> with no truncation, so a large table bloats the pre-rendered HTML and slows the page. For big datasets, paginate or load the data at runtime rather than baking thousands of rows into the static output.
Free tier
402 Pro requiredThis is a Pro feature; the Free tier can't run it. Upgrade to Pro or higher to generate the table.
Frequently asked questions
Should I use dangerouslySetInnerHTML to embed the HTML in a Next.js page?
For static, trusted spreadsheet data baked at build time, dangerouslySetInnerHTML is acceptable — and the tool already HTML-escapes &, <, >, and " in every cell, so injected markup renders as text. If you want editable JSX instead, convert class= to className=. Never use dangerouslySetInnerHTML for untrusted third-party content.
Can I use this with Next.js MDX pages?
Yes. MDX renders embedded HTML, so paste the snippet (with its class attributes) directly into your .mdx file — no conversion to className needed. Just make sure the MDX content directory is in your Tailwind content globs so the classes aren't purged.
Does the table HTML work with Tailwind's content/purge config?
Only if the file holding the table is matched by your Tailwind content array. The build strips any utility class it can't find in the content sources, so add the page or MDX file path to content and rebuild. This is the single most common reason a generated table looks unstyled in production.
Does the table update automatically when I change the spreadsheet?
No. The embed is a one-time paste of static HTML — there is no live data binding. After editing the spreadsheet, re-export from the tool, re-paste, and rebuild. For frequently changing data, build a real data pipeline instead of inline HTML.
Which sheet and row does it read?
The first sheet only (index 0), with row 1 as the <th> headers. There is no sheet picker, so keep the data you want to embed on the first tab.
Where do the dark mode classes go, and what config do I need?
dark:bg-gray-800 dark:text-gray-100 is appended to the wrapper <div>, every <th>, and every <tr> (not <td> or <table>). For them to work, set darkMode: 'class' in your Tailwind config and toggle a dark class on a parent element.
Does it ship a Tailwind CDN tag for the static page?
No. The output is the snippet only — no CDN <script>/<link> and no full HTML document. A Next.js project builds Tailwind itself, so the snippet relies on your existing Tailwind setup.
Are formatted dates and numbers preserved at build time?
Yes — cells export as their formatted display text, so a date shown as 2026-03-15 or a number shown as 1,200 is baked in as that string. It does not export raw serials. Standardise the sheet first if you need raw values (for example with the Excel date standardizer).
Can it read a CSV as well as an xlsx?
Yes — both .xlsx and .csv are accepted, with the first row treated as headers.
Does embedding a huge table hurt my static site?
It can. Every row becomes an inline <tr> with no truncation, so a large table bloats the pre-rendered HTML and slows the page. Keep inline static tables to a reasonable size; paginate or fetch big datasets at runtime.
Does my spreadsheet upload during authoring?
No. SheetJS parses the file and generates the HTML entirely in your browser. Nothing is sent to a server while you author the table.
Can I get the data as Python, an SVG chart, or a tRPC router for a data-driven page instead?
Yes — sibling tools cover those: the Excel to Python generator emits a data structure, the Excel SVG dataviz tool renders a chart, and the Excel to tRPC router tool scaffolds an API for a data-driven (non-static) page.
Privacy first
Every JAD Excel tool runs entirely in your browser using SheetJS and ExcelJS. Your spreadsheets, formulas, and data never leave your device — verified by zero outbound network requests during processing.